Images fluctuating on scroll in ios - ios

i am getting images from url's and displaying it in tableview which happens successfully but when i scroll it automatically and repeatedly changes but after i pause at that point for a couple of seconds (roughly 10 or more which is a pretty long time) the proper image loads.
func load_image(urlString:String)
{
let imgURL: NSURL = NSURL(string: urlString)!
let request: NSURLRequest = NSURLRequest(URL: imgURL)
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(request){
(data, response, error) -> Void in
if (error == nil && data != nil)
{
func display_image()
{
cell.pic.image = UIImage(data: data!)
}
dispatch_async(dispatch_get_main_queue(), display_image)
}
}
task.resume()
}
load_image(urls[indexPath.row])

First of all, why are You loading images in, I guess, cellForRowAtIndexPath?
You can load it in viewDidLoad and store in some array. But if You need to do this as You are doing...
For better performance You can use NSCache to prevent loading images everytime tableview will show your cell. Try something like that:
let imageCache = NSCache()
func load_image(urlString:String)
{
if let imageFromCache = imageCache.objectForKey(urlString) as? UIImage {
cell.pic.image = imageFromCache
return
}
let imgURL: NSURL = NSURL(string: urlString)!
let request: NSURLRequest = NSURLRequest(URL: imgURL)
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(request){
(data, response, error) -> Void in
if (error == nil && data != nil)
{
func display_image()
{
let imageToCache = UIImage(data: data!)
cell.pic.image = UIImage(data: data!)
imageCache.setObject(imageToCache!, forKey: urlString)
}
dispatch_async(dispatch_get_main_queue(), display_image)
}
}
task.resume()
}
For more informations about NSCache you can check documentation.

Your cell is being reused when scrolling. The cellForRowAt method will be called on scrolling.
Set imageView.image = nil in cellForRowAt method so no image will be display on the image view until they get downloaded.
We make imageView.image = nil is because if no image available for that cell then no image should be shown for that cell.

Related

Nil while caching images

I've been able to solve the issue of caching images to improve scroll performance in my app. However nil is found when it tries to add it to cache. Also how can I add a placeholder image for images that failed to load or aren't available ?
let imageCache = NSCache<NSString, UIImage>()
extension UIImageView {
func downloadImage(from imgURL: String) -> URLSessionDataTask? {
guard let url = URL(string: imgURL) else { return nil }
// set initial image to nil so it doesn't use the image from a reused cell
image = nil
// check if the image is already in the cache
if let imageToCache = imageCache.object(forKey: imgURL as NSString) {
self.image = imageToCache
return nil
}
// download the image asynchronously
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
if let err = error {
print(err)
return
}
DispatchQueue.main.async {
// create UIImage
let imageToCache = UIImage(data: data!)
// add image to cache
imageCache.setObject(imageToCache!, forKey: imgURL as NSString)
self.image = imageToCache
}
}
task.resume()
return task
}
}
A couple of observations:
Just supply placeholder as parameter to function and use it instead of nil to initialize the image.
Do that after checking the cache (because there’s no point in using the placeholder if you found desired image in the cache).
Avoid use of ! forced unwrapping operator.
Check that UIImage(data:) found an image in the guard statement (and on the session queue, not the main thread).
Thus:
let imageCache = NSCache<NSString, UIImage>()
extension UIImageView {
func downloadImage(from imgURL: String, placeholder: UIImage? = nil) -> URLSessionDataTask? {
guard let url = URL(string: imgURL) else { return nil }
// check if the image is already in the cache
if let imageToCache = imageCache.object(forKey: imgURL as NSString) {
image = imageToCache
return nil
}
// set initial image to placeholder so it doesn't use the image from a reused cell
image = placeholder
// download the image asynchronously
let task = URLSession.shared.dataTask(with: url) { data, _, error in
guard
let data = data,
error == nil,
let imageToCache = UIImage(data: data)
else {
print(error ?? URLError(.badServerResponse))
return
}
imageCache.setObject(imageToCache, forKey: imgURL as NSString)
DispatchQueue.main.async {
self.image = imageToCache
}
}
task.resume()
return task
}
}

Swift Image Cache not Reloading

I'm having problems cacheing for images from JSON correctly with this UIImageView extension. The images load correctly when I first open the app and scroll down the page. However when I scroll back up, they don't reload and are completely gone. Can anyone see anything wrong with the code?
let imageCache = NSCache<AnyObject, AnyObject>()
extension UIImageView {
func loadImageUsingUrlString(urlString: String) {
let url = NSURL(string: urlString)
if let imageFromCache = imageCache.object(forKey: urlString as AnyObject) as? UIImage {
self.image = imageFromCache
return
}
URLSession.shared.dataTask(with: url! as URL) { (data, response, error) in
if error != nil {
print(error ?? "URLSession error")
return
}
DispatchQueue.main.async {
let imageToCache = UIImage(data: data!)
imageCache.setObject(imageToCache!, forKey: urlString as AnyObject)
self.image = imageToCache
}
}.resume()
}
}
Here is the snippet from the cell.swift file
let imageCache = NSCache<AnyObject, AnyObject>()
func setupThumbnailImage() {
if let thumbnailImageUrl = television?.poster_url {
let urlPrefix = "https://www.what-song.com"
let urlSuffix = thumbnailImageUrl
let urlCombined = urlPrefix + urlSuffix
thumbnailImageView.loadImageUsingUrlString(urlString: urlCombined)
}
}
I suggest using kingFisher, it is very easy to use and it manages all starting from cache threads etc.
let imageResource = ImageResource(downloadURL:URL(string: imagePath )!,cacheKey: imagePath )
viewImage.kf.indicatorType = .activity
viewImage.kf.setImage(with: resource)
where imagePath is the url of your image and viewImage is your imageView
Most probably you would be calling it in wrong way.
Remember that in tableView you reuse the cells.
By the time response comes back for the URLSessionTask you would have already scrolled up/down. In that case self.image would be assigned to the currently visible cell.
Please add your cellForRow code in question.

How to download image from web?

I am new to iOS i want download image to display it is working code but here lot of code duplication
let url = URL(string: iteminfo.imageUrl!)
let urlRequest = URLRequest(url: url!)
let task = URLSession.shared.dataTask(with: urlRequest) { (data, response, error) in
if error != nil {
print(error)
}
if let data = data {
print(data)
self.imageViewItemPic.image = UIImage(data: data)
}
}
task.resume()
let url2 = URL(string: iteminfo.cookerProfilePicUrl!)
let urlRequest2 = URLRequest(url: url2!)
let task2 = URLSession.shared.dataTask(with: urlRequest2) { (data, response, error) in
if error != nil {
print(error)
}
if let data = data {
print(data)
self.imageViewCookerProfilePic.image = UIImage(data: data)
}
}
task2.resume()
So I want to reuse my code but i unfortunately i can not reach my goal. there have no error and url is correct . every time goes else statement . i am missing something but what is that ?
if let image = downlaodImage(urlImage: iteminfo.imageUrl){
print("first \(image)")
imageViewItemPic.image = image
}else{
print("first wrong......")
}
if let image = downlaodImage(urlImage: iteminfo.cookerProfilePicUrl){
print("second \(image)")
imageViewCookerProfilePic.image = image
}
else{
print("second wrong......")
}
Here is my method :
func downlaodImage(urlImage : String?) -> UIImage?{
var image : UIImage?
let url = URL(string: urlImage!)
let urlRequest = URLRequest(url: url!)
let task = URLSession.shared.dataTask(with: urlRequest) { (data, response, error) in
if let data = data {
// print(data)
image = UIImage(data: data)
}
}
task.resume()
return image
}
note: I am not sure is it best way or not . if it is not best practice feel free to guide me .
There's no need of so much hassle. You have the URL of the image so you can simply download the image from the URL. For example:
func downloadImage(imageURL: String) {
DispatchQueue.global().async {
let data = NSData.init(contentsOf: NSURL.init(string: imageURL) as! URL)
DispatchQueue.main.async {
let image = UIImage.init(data: data as! Data)
imageView.image = image
}
}
}
Edit: To reuse this code I would suggest to use extension of UIImageView. Here's an example:
extension UIImageView {
func setImageFromURL(url: String) {
DispatchQueue.global().async {
let data = NSData.init(contentsOf: NSURL.init(string: url) as! URL)
DispatchQueue.main.async {
let image = UIImage.init(data: data as! Data)
self.image = image
}
}
}
}
Use this method whenever you want to set the image of an imageView from a url like this:
self.imageViewCookerProfilePic.setImageFromURL(url: "https://encrypted-tbn1.gstatic.com/images?q=tbn:ANd9GcQNpKmjx1w3DRDZ9IXN81-uhSUA6qL6obkOthoUkb9RZkXf5pJ8")
Dude. You should learn some staff about async and sync code.
Here is the thing. Code in you downloadImage works synchronically, so it pass you URLTask and go straight to return, there you return you image variable, that is nil.
One of the solutions in to use callback block like this:
func downloadImage(urlImage : String?, complete: ((UIImage?)->Void)? = nil){
let url = URL(string: urlImage!)
let urlRequest = URLRequest(url: url!)
let task = URLSession.shared.dataTask(with: urlRequest) { (data, response, error) in
if let data = data {
complete?(UIImage(data: data))
}
}
task.resume()
}
And then use it like this:
{ ...
downloadImage(urlImage: "", complete: { image in
if let image = image{
self.imageViewItemPic.image = image
}else{
print("no image")
}
})
...
}
You should read some tutorials about async code and web in swift. You could start with this site
downlaodImage() downloads an image asynchronously so
if let image = downlaodImage(...) { ... }
is always going to fail because program execution has continued before your response data has come back.
It would be easier just to set your images in the callback function closure of downlaodImage() as below by adding a UIImageView parameter to downlaodImage(). This way you can reduce the repetition of if else blocks by moving them to the downlaodImage function.
func downlaodImage(urlImage : String?, imageView: UIImageView) -> UIImage?{
var image : UIImage?
let url = URL(string: urlImage!)
let urlRequest = URLRequest(url: url!)
let task = URLSession.shared.dataTask(with: urlRequest) { (data, response, error) in
if let data = data {
// print(data)
if let image = UIImage(data: data) {
imageView.image = image
} else {
print("failed to load image")
}
}
}
task.resume()
return image
}
Simplified code without if/else blocks
downlaodImage(urlImage: "https://encrypted-tbn1.gstatic.com/images?q=tbn:ANd9GcQNpKmjx1w3DRDZ9IXN81-uhSUA6qL6obkOthoUkb9RZkXf5pJ8", imageView: imageViewItemPic)
downlaodImage(urlImage: "https://www.dominos.co.nz/ManagedAssets/OLO/eStore/all/Product/NZ/P015/P015_ProductImage_Small_en_Default_20140203_105245.png", imageView: imageViewCookerProfilePic)

Adding new UIImage to SubView takes very long

In my first swift project I face some problems by adding a new UIImage to the SubView after downloading the content of this image.
It looks like the download is done pretty fast but the App needs another 5-15 seconds to update the view. I have no clue why.
Here is what I have done:
override func viewDidLoad() {
super.viewDidLoad()
//...
loadPic(PrevPic)
}
func loadPic(prevImage: UIImageView){
//... get URL; result in object["data"]
let fileUrl = NSURL(string: object["data"] as! String)
let session = NSURLSession.sharedSession()
let request = NSMutableURLRequest(URL: fileUrl!)
let task = session.dataTaskWithRequest(request){
(data, response, error) -> Void in
if error != nil {
print(error!.localizedDescription)
} else {
var imagePic: UIImageView?
let imageData = NSData(data: data!)
prevImage.removeFromSuperview()
let image: UIImage = UIImage(data: imageData)!
imagePic = UIImageView(image: image)
imagePic?.contentMode = .ScaleAspectFit
self.view.addSubview(imagePic!)
//... alignment
}
}
task.resume()
//...
}
An ideas why?
Thanks for the support.
You dont have to remove UIImageView from your view and add it back. That is taking a lot of time. You should replace imageView's old image with new one.You can use following code.
if let newImage = UIImage(data: imageData){
imageView.image = newImage
}
When call for the web service then the task execute on main thread thats why the UI become freeze and none responsive. To get rid of this problem call the main queue and the update the UI like this:
func loadPic(prevImage: UIImageView){
//... get URL; result in object["data"]
let fileUrl = NSURL(string: object["data"] as! String)
let session = NSURLSession.sharedSession()
let request = NSMutableURLRequest(URL: fileUrl!)
let task = session.dataTaskWithRequest(request){
(data, response, error) -> Void in
if error != nil {
print(error!.localizedDescription)
} else {
var imagePic: UIImageView?
let imageData = NSData(data: data!)
dispatch_async(dispatch_get_main_queue(), {
prevImage.removeFromSuperview()
let image: UIImage = UIImage(data: imageData)!
imagePic = UIImageView(image: image)
imagePic?.contentMode = .ScaleAspectFit
self.view.addSubview(imagePic!)
//... alignment
})
}
}
task.resume()
//...
}
The basic idea is
dispatch_async(dispatch_get_main_queue(), {
// update UI
})

Populate collectionView from JSON image url swift

I want to Load image using JSON parsing in collectionView. I'm getting array of image URLs, but images are not shown on UIImageView. I have used this code after getting JSON array and displaying it in UICollectionView's UIImageView.
if let url = NSURL(string: self.Products[indexPath.row].image) {
if let data = NSData(contentsOfURL: url){
cell.product_image.contentMode = UIViewContentMode.ScaleAspectFit
cell.product_image.image = UIImage(data: data)
}
}
But I am not able to load image, but text is displayed. I have used this code in cellForItemAtIndexPath method.. Can anyone suggest me what am I doing wrong?
func jsonParsingFromURL(){
// 1
let reposURL = NSURL(string: "http://api.room2shop.com/api/product/GetProducts?categoryId=24&filter=2&pageNumber=1")
// 2
if let JSONData = NSData(contentsOfURL: reposURL!) {
// 3
do
{
if let json = try NSJSONSerialization.JSONObjectWithData(JSONData, options: .AllowFragments) as? NSDictionary {
// 4
if let reposArray = json["ProductList"] as? [NSDictionary] {
// 5
for item in reposArray {
Products.append(ProductList(json: item))
}
}
}
}
catch {
print("Error with Json: \(error)")
}
}
}
This is my JSON parsing code
to reflect changes, you need to use
self.collectionView?.reloadData()
EDIT
1- please replace this block with this, and tell me if you get the urls normally, for me I get the urls normally
for item in reposArray
{
//ProductList(json: item)
//Products.append(ProductList(json: item))
//print(item)
print("__________")
print(item["Image"]!)
}
2- i was getting
Transport Security has Blocked a cleartext HTTP
solution was here.
Transport security has blocked a cleartext HTTP
use this
// taking the URL , then request image data, then assigne UIImage(data: responseData)
let imgURL: NSURL = NSURL(string: "www.example.com/image.jpg")!
let request: NSURLRequest = NSURLRequest(URL: imgURL)
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(request) {
(data,response,error) -> Void in
if ( error == nil && data != nil ) {
func display_image() {
// imageView.post.image = your UIImage
self.imageViewPost.image = UIImage(data: data!)
}
dispatch_async(dispatch_get_main_queue(), display_image)
}
}
task.resume()
// end of loading img
The image is a URL which is not saved locally inside the image.xassets, so you need to parse the URL using the extension given below.
// add the extension
extension UIImageView {
func loadImage(urlString: String) {
let url = URL(string: urlString)!
URLSession.shared.dataTask(with: url, completionHandler: { (data, respones, error) in
if error != nil {
print(error ?? "")
return
}
DispatchQueue.main.async {
self.image = UIImage(data: data!)
}
}).resume()
}
}
// call this statement in your vc (where you polpulate)
yourImageView.loadImage(urlString:yourUrl)

Resources