What is the best way to async call to load a UIImage to a textView as a NSTextAttachment in a tableView? So far this is working very badly.
I am using a URL string to load a single image inside multiple tableView cells.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell")
//Transform Data From ^ to load at the bottom
tableView.transform = CGAffineTransform (scaleX: 1,y: -1);
cell?.contentView.transform = CGAffineTransform (scaleX: 1,y: -1);
cell?.accessoryView?.transform = CGAffineTransform (scaleX: 1,y: -1);
let username = cell?.viewWithTag(1) as! UITextView
username.text = messageArray[indexPath.row].username
let message = cell?.viewWithTag(2) as! UITextView
//message.text = messageArray[indexPath.row].message // delete later
var test = messageArray[indexPath.row].uploadedPhotoUrl
print(test ?? String.self)
if(test != ""){
// create an NSMutableAttributedString that we'll append everything to
let fullString = NSMutableAttributedString(string: "")
// create our NSTextAttachment
let image1Attachment = NSTextAttachment()
URLSession.shared.dataTask(with: NSURL(string: messageArray[indexPath.row].uploadedPhotoUrl)! as URL, completionHandler: { (data, response, error) -> Void in
if error != nil {
print(error ?? String())
return
}
DispatchQueue.main.async(execute: { () -> Void in
let image = UIImage(data: data!)
image1Attachment.image = image
//calculate new size. (-20 because I want to have a litle space on the right of picture)
let newImageWidth = (message.bounds.size.width - 20 )
//resize this
image1Attachment.bounds = CGRect.init(x: 0, y: 0, width: newImageWidth, height: 200)
// wrap the attachment in its own attributed string so we can append it
let image1String = NSAttributedString(attachment: image1Attachment)
// add the NSTextAttachment wrapper to our full string, then add some more text.
fullString.append(image1String)
fullString.append(NSAttributedString(string: message.text))
// draw the result in a label
message.attributedText = fullString
//message.textStorage.insert(image1String, at: message.selectedRange.location)
message.textColor = .white
test = ""
})
}).resume()
}else {
message.text = messageArray[indexPath.row].message
}
let timeStamp = cell?.viewWithTag(3) as! UILabel
timeStamp.text = messageArray[indexPath.row].timeStamp
let imageView = cell?.viewWithTag(4) as! UIImageView
imageView.image = nil
let urlString = messageArray[indexPath.row].photoUrl
imageView.layer.cornerRadius = 10
imageView.clipsToBounds = true
//Load profile image(on cell) with URL & Alamofire Library
let downloadURL = NSURL(string: urlString!)
imageView.af_setImage(withURL: downloadURL! as URL)
return cell!
}
Images are still lagging when index is scrolling(appearing and disappearing) and are also not loading completely
You are loading an image at a time while the tableviewcell is scrolling. There is some time to call the service and then wait for the response to be returned, thus affecting the scrolling although it is on another thread.
You can try calling the images in batches of maybe 10 or 20 at a time.
TL/DR:
Learn to write better code.
For starters, there's no way tableView(:cellForRowAt:) can accomplish all that work in under 16 milliseconds. I think you should reconsider your app structure and architecture for starters. It would serve your app better to abstract the networking calls to an API that can run on a background thread(s).
One way would be to abstract away a lot of the implementation to anOperationQueue Ray Wenderlich has a couple tutorials on how this works. This particular one was written for Swift 1.2, however the principles for Operation are there.
Related
I hope you can help me with this one.
I am trying to download an image from a url and the set it to a label. I am able to show it in a imageView but not a label.
This is my code.
func updateImage() {
let ref = Database.database().reference()
let uid = Auth.auth().currentUser?.uid
let usersRef = ref.child("users").child(uid!)
// only need to fetch once so use single event
Database.database().reference().child("Products").queryOrderedByKey().observe(.childAdded, with: { snapshot in
if !snapshot.exists() { return }
//print(snapshot)
let userInfo = snapshot.value as! NSDictionary
print(userInfo)
print(userInfo["name"]!)
let profileUrl = userInfo["photoURL"] as! String
print(profileUrl)
let storageRef = Storage.storage().reference(forURL: profileUrl)
storageRef.downloadURL(completion: { (url, error) in
do {
let data = try Data(contentsOf: url!)
let image = UIImage(data: data as Data)
self.productPhoto.image = image
}
catch _ {
// Error handling
}
})
})
}
I have tried many things like this
self.swipeLabel.backgroundColor = UIColor(patternImage: UIImage(named: image))
Thank you in advance
Try this as I think what you are trying to do is setting the Background image to UILabel.
Let's say you have got image from data as:
let data = try Data(contentsOf: url!)
let image = UIImage(data: data as Data)
Now you can set this image to UILabel with text by using the Attributed string as following :
let imageAttachment = NSTextAttachment()
imageAttachment.image = image
//Set bound to reposition
let imageOffsetY:CGFloat = -5.0;
imageAttachment.bounds = CGRect(x: 0, y: imageOffsetY, width: imageAttachment.image!.size.width, height: imageAttachment.image!.size.height)
//Create string with attachment
let attachmentString = NSAttributedString(attachment: imageAttachment)
//Initialize mutable string
let completeText = NSMutableAttributedString(string: "")
//Add image to mutable string
completeText.append(attachmentString)
//Add your text to mutable string
let textAfterIcon = NSMutableAttributedString(string: "Using attachment.bounds!")
completeText.append(textAfterIcon)
self.label.textAlignment = .center;
self.label.attributedText = completeText;
or If you want to set image to background then you can try adding imageview as UILabel's subview or vice versa as following :
label.addSubview(imageView) or imageView.addSubview(label)
Also you can try this to directly set image to Background of the UILabel :
label.backgroundColor = UIColor(patternImage: UIImage(named: "backgroundImage")!)
I'm new at Xcode and swift and ran into this bug. I've searched around a bit and could not find anything on this topic. I have an extension for UIImage that allows me to cache images to the phone here :
import UIKit
let imageCache = NSCache<AnyObject, AnyObject>()
extension UIImageView{
func loadImageUsingCacheWithUrlString(urlString : String)
{
self.image = nil;
// check cache for image first
if let cachedImage = imageCache.object(forKey: urlString as AnyObject) as? UIImage {
self.image = cachedImage;
return
}
let url = URL(string: urlString)
URLSession.shared.dataTask(with: url!, completionHandler: { (data, response, error) in
//download hit an error
if error != nil {
print(error!)
return
}
DispatchQueue.main.async {
if let downloadedImage = UIImage(data: data!){
imageCache.setObject(downloadedImage, forKey: urlString as AnyObject)
self.image = downloadedImage
}
}
}).resume()
}
}
It is not loading the image into a table views image view:( Ignore random text )
Table view not loading image
Here is also the UItableView from the main.storyboard:
Updated main.storyboard screen shot
Here is my cellForRowAt: indexPath method where the image is suppose to be loaded:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cellid" , for: indexPath) as! CustomChatTableViewCell;
let gray = UIColor(red:0.94, green:0.94, blue:0.94, alpha:1.0)
let red = UIColor(red:1.00, green:0.22, blue:0.37, alpha:1.0)
let message = messages[indexPath.item]
if message.toId == user?.toId{
cell.messageBackground.backgroundColor = red
cell.messageLabel.textColor = UIColor.white
}
else{
cell.messageBackground.backgroundColor = gray
cell.messageLabel.textColor = UIColor.black
}
cell.messageLabel.text = message.text
if let imageUrl = message.imageUrl{
print(imageUrl)
cell.messageImage.loadImageUsingCacheWithUrlString(urlString: imageUrl)
cell.messageImage.isHidden = false;
cell.messageLabel.isHidden = true
//cell.messageBackground.isHidden = true;
}
else
{
cell.messageImage.isHidden = true;
cell.messageLabel.isHidden = false
cell.messageBackground.isHidden = false;
}
return cell;
}
Expected Result:
Images load into cells
Observed Result
Images dont load into the cells :(
these lines of code :
if let imageUrl = message.imageUrl{
print(imageUrl)
cell.messageImage.loadImageUsingCacheWithUrlString(urlString: imageUrl)
Actually print a valid URL string for an image on my firebase database, which is confusing because It is not loading the image.
Important
I use the loadImageUsingCacheWithUrlString method in other parts of my project and it works fine so I don't think its the method.... whats going on?? thank you so much if you can solve this you are an amazing coder!!
I can put an image in the main.storyboard and it works... so I dont know what could be going wrong... :(
screen shot of updated main.storyboard
Image seems to be fine in Extension :
code with breakpoint and console showing
Not sure If the image Is being covered Up in Capture View Hierarchy :
View Hierarchy
In your storyboard, the imageView messageImage is a subview of the view messageBackground. In your if-statement in cellForRow method, you are setting the messageBackground to be hidden
if let imageUrl = message.imageUrl{
print(imageUrl)
cell.messageImage.loadImageUsingCacheWithUrlString(urlString: imageUrl)
cell.messageImage.isHidden = false;
cell.messageLabel.isHidden = true
cell.messageBackground.isHidden = true; //THIS IS THE CULPRIT
}
Since messageBackground is hidden, it's subviews are hidden as well. Might need to rethink your business logic here.
Hi i am making an application in Xcode and using swift for that. I am downloading images from Firebase and show them in the table view. There are some problems with that. But first i will show the code.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "customCell", for: indexPath) as! FrontViewCell
cell.contentView.backgroundColor = UIColor.clear
//let whiteRoundedView : UIView = UIView(frame: CGRect(10, 8, self.view.frame.size.width - 20, 149))
let whiteRoundedView: UIView = UIView(frame: CGRect(x: 10, y: 8, width: self.view.frame.width - 20, height: 200))
whiteRoundedView.layer.backgroundColor = CGColor(colorSpace: CGColorSpaceCreateDeviceRGB(), components: [1.0, 1.0, 1.0, 0.8])
whiteRoundedView.layer.masksToBounds = false
whiteRoundedView.layer.cornerRadius = 2.0
whiteRoundedView.layer.shadowOffset = CGSize(width: -1, height: 1)
whiteRoundedView.layer.shadowOpacity = 0.2
cell.contentView.addSubview(whiteRoundedView)
cell.contentView.sendSubview(toBack: whiteRoundedView)
//cell.categoryImageView.image = catImages[indexPath.row]
//print("Product \(allCats[indexPath.row].name)")
cell.categoryLabel.text = allCats[indexPath.row].name
if let n = allCats[indexPath.row].name{
con?.storage?.reference(withPath: "categories/\(n).png").data(withMaxSize: 10 * 1024 * 1024, completion: {
data, error in
if error == nil{
let im = UIImage(data: data!)
cell.categoryImageView.image = im
cell.layoutSubviews()
}
else{
print("Error Downloading Image \(error?.localizedDescription)")
}
})
}
return cell
}
So above is the code to set the images to an imageView in the cell.
Problems
When i scroll down and then scroll up again, the images are different in the same cells.
The tableview scrolling is very laggy.
These are the problems. Please let me know how can i solve this?
I know of a library SDWebImage but i don't know how to download Firebase image with that library. Please help me through this problem. I am very exhausted by this problem. I have been trying to solve it for the last 20 hours without sleep but could not. Please let me know what i am doing wrong and how should i fix that. Thanks.
TableView is laggy because you are redownloading images all the time.
This is a caching issue.
As for the images being different in the same cell, you can change this just by resseting the image to nil, because cells are being reused, they are using a previous image, while the new one downloads.
But both of these issues would be fixed if you were to use some caching framework, for example, probably the best one out there is SDWebImage.
If you don't wanna use a library for this. Here is the most basic implementation of caching images.
let imageCache = NSCache<AnyObject, AnyObject>()
extension UIImageView {
func loadImageUsingCacheWithUrlString(_ urlString: String) {
self.image = nil
//check cache for image
if let cachedImage = imageCache.object(forKey: urlString as AnyObject) as? UIImage {
self.image = cachedImage
return
}
//otherwise start the download
let url = URL(string: urlString)
URLSession.shared.dataTask(with: url!, completionHandler: { (data, response, error) in
//there was an error with the download
if error != nil {
print(error ?? "")
return
}
DispatchQueue.main.async(execute: {
if let downloadedImage = UIImage(data: data!) {
imageCache.setObject(downloadedImage, forKey: urlString as AnyObject)
self.image = downloadedImage
}
})
}).resume()
}
}
Usage:
cell.categoryImageView.loadImageUsingCacheWithUrlString("your firebase url string")
EDIT: Yes, you can use this to download images that are stored in Firebase.
EDIT: This code will solve your issues, but memory management is not considered here, for a serious production app I would suggest looking into libraries dedicated to image caching.
EDIT: I just noticed that there is proper info on Firebase documentation , showing how it works with SDWebImage. Check it out: SDWebImage + Firebase
I'm currently reading images from my firebase storage - which works fine.
I have set up a caching to read images from the cache when it has been read from the storage:
// Storage.imageCache.object(forKey: post.imageUrl as NSString)
static func getImage(with url: String, completionHandler: #escaping (UIImage) -> ())
{
if let image = imageCache.object(forKey: url as NSString)
{
print("CACHE: Unable to read image from CACHE ")
completionHandler(image)
}
else
{
let ref = FIRStorage.storage().reference(forURL: url)
ref.data(withMaxSize: 2 * 1024 * 1024)
{
(data, error) in
if let error = error
{
print("STORAGE: Unable to read image from storage \(error)")
}
else if let data = data
{
print("STORAGE: Image read from storage")
if let image = UIImage(data: data)
{
// Caches the image
Storage.imageCache.setObject(image, forKey: url as NSString)
completionHandler(image)
}
}
}
}
}
}
But its not working. It seems to not work at all as well, I don't have the message ' print("CACHE: Unable to read image from CACHE ")
' being displayed on my console but the print ' print("STORAGE: Image read from storage")
'
Do you know how this can be achieved by any chance please?
Thanks a lot for your time!
---EDIT --
I call the image in table cell view from firebase storage then as:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = self.feedTableView.dequeueReusableCell(withIdentifier: "MessageCell")! as UITableViewCell
let imageView = cell.viewWithTag(1) as! UIImageView
let titleLabel = cell.viewWithTag(2) as! UILabel
let linkLabel = cell.viewWithTag(3) as! UILabel
titleLabel.text = posts[indexPath.row].title
titleLabel.numberOfLines = 0
linkLabel.text = posts[indexPath.row].link
linkLabel.numberOfLines = 0
Storage.getImage(with: posts[indexPath.row].imageUrl){
postPic in
imageView.image = postPic
}
return cell
}
You can realize caching images with Kingfisher for example. And works better. link
How to use: Add link to your image from storage to database item node. Like this:
Then just use it to present and cache image.
Example:
let imageView = UIImageView(frame: frame) // init with frame for example
imageView.kf.setImage(with: <urlForYourImageFromFireBase>) //Using kf for caching images
Hope it helps
I have a problem. My UICollectionView's scroll is too laggy because of the size of the images that are in cells. Image's resizing function changes almost nothing. And I have no ability to make a small copies of the images, because of the images' number.
So what should I do if I need to make a quick images loading just like in native Album app? I heard about "lazy loading", but I can't find any tutorial in Swift about it.
Here is my cellForItemAtIndexPath:
let cell : MyCollectionViewCell = collectionView.dequeueReusableCellWithReuseIdentifier("MyCell", forIndexPath: indexPath) as! MyCollectionViewCell
let painting = objs[indexPath.row]
let name = (painting.valueForKey("imageName") as? String)!
let path = NSBundle.mainBundle().bundlePath.stringByAppendingString("/\(name)") as NSString
cell.imageView.image = UIImage(contentsOfFile: path as String)
cell.layer.shouldRasterize = true
cell.layer.rasterizationScale = UIScreen.mainScreen().scale
return cell
I would be glad if you help me to solve this problem!
PS The app doesn't load it from the WEB. All images are in the app on the device.
UPDATE
Finally, I implemented SDWebImage. This is the best solution I found.
Here is my cellForRowAtIndexPath:
let cell : MyCollectionViewCell = collectionView.dequeueReusableCellWithReuseIdentifier("MyCell", forIndexPath: indexPath) as! MyCollectionViewCell
let block: SDWebImageCompletionBlock! = {(image: UIImage!, error: NSError!, cacheType: SDImageCacheType, imageURL: NSURL!) -> Void in
// println(self)
}
let path = NSBundle.mainBundle().bundlePath.stringByAppendingString("/\(images[indexPath.row])") as NSString
let url = NSURL.fileURLWithPath(path as String)
println(url)
cell.imageView.sd_setImageWithURL(url, placeholderImage: UIImage(named: "placeholder"), completed: block)
cell.layer.shouldRasterize = true
cell.layer.rasterizationScale = UIScreen.mainScreen().scale
return cell
It is because you are loading image in main thread, better do it on background thread, to make it more smooth better decode image on background thread as well. it you will set image directly it will decode when you will set image to imageView, so better first decode it and then set it to imageView.image
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), { () -> Void in
var image = UIImage(contentsOfFile: path as String)
UIGraphicsBeginImageContextWithOptions(image!.size, true, 0);
image?.drawAtPoint(CGPointZero)
image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
dispatch_async(dispatch_get_main_queue(), { () -> Void in
cell.imageView.image = image
})
Why don't you use:
let name = (painting.valueForKey("imageName") as? String)!
cell.imageView.image = UIImage(named: name)
instead of using NSBundleas loading image with name method caches the image