I want to get an image from a url. When using Apple's api's it works but when I use SDWebImage (version 3.8) I get an error.
Using Apple's api (this works):
if let url = NSURL(string: testURL) {
if let data = NSData(contentsOfURL: url) {
let testImage = UIImage(data: data)
cell.personAvatar.image = testImage
}
}
Using SDWebImage (does not work):
cell.personAvatar.sd_setImageWithURL(NSURL(string: testURL)!, placeholderImage: UIImage(named: "testImage")!) { (image, error, SDImageCacheType, url) in
cell.personAvatar.image = image
}
I am making these calls inside of:
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell.
The error I get when using SDWebImage is:
Error Domain=NSURLErrorDomain Code=-999 "cancelled" UserInfo={NSErrorFailingURLKey=
Any ideas why this is not working?
Related
I have a problem. I have this code in Swift but when i scrolling in the tableview its very laggy and i dont know what the problem is..??
Its downloading image data that is like 20mb each (i think)..
Thanks!
func downloadJsonWithTask() {
let url = NSURL(string: urlString)
var downloadTask = URLRequest(url: (url as? URL)!, cachePolicy: URLRequest.CachePolicy.reloadIgnoringCacheData, timeoutInterval: 200)
downloadTask.httpMethod = "GET"
URLSession.shared.dataTask(with: downloadTask, completionHandler: {(data, response, error) -> Void in
let jsonData = try? JSONSerialization.jsonObject(with: data!, options: .allowFragments)
print(jsonData)
}).resume()
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as! TableViewCell
cell.nameLabel.text = nameArray[indexPath.row]
cell.dobLabel.text = dobArray[indexPath.row]
let imgURL = NSURL(string: imgURLArray[indexPath.row])
if imgURL != nil {
let data = NSData(contentsOf: (imgURL as? URL)!)
cell.imgView.image = UIImage(data: data as! Data)
}
return cell
}
///for showing next detailed screen with the downloaded info
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let vc = self.storyboard?.instantiateViewController(withIdentifier: "DetailViewController") as! DetailViewController
vc.imageString = imgURLArray[indexPath.row]
vc.imageString2 = imgURL2Array[indexPath.row]
vc.imageString3 = imgURL3Array[indexPath.row]
vc.imageString4 = imgURL4Array[indexPath.row]
vc.imageString5 = imgURL5Array[indexPath.row]
vc.imageString6 = imgURL6Array[indexPath.row]
vc.nameString = nameArray[indexPath.row]
vc.dobString = dobArray[indexPath.row]
vc.txtString = txtArray[indexPath.row]
vc.contactString = contactArray[indexPath.row]
self.navigationController?.pushViewController(vc, animated: true)
}
The problem because of this line
let data = NSData(contentsOf: (imgURL as? URL)!)
it blocks the main thread , you have to run it in other queue , beside it re downloads the image every scroll ( no cache ) so it's better to use SDWebImage
cell.imgView.sd_setImage(with: URL(string: "http://www.example.com/path/to/image.jpg"), placeholderImage: UIImage(named: "placeholder.png"))
This will always lag.
Your requesting image in main thread.
Try to use SDWebCache Library : https://github.com/rs/SDWebImage .
1 : It will cachce intially image and will avoid multiple HTTP requests.
2 : Works in background so will not affect the main thread.
Use method imageview.sd_imageFromUrl(URL(....)!)
It's very clear. You asking the Image data from main thread. Whenever you perform timeconsuming operation on the Main Thread, then the App will be laggy.
let data = NSData(contentsOf: (imgURL as? URL)!)
The best solution that worked for me so far is using AlamofireImage or Kingfisher. That needs you to learn about adding library into your project. Try to use Cocoapods for convenience.
I assume you were able to use Cocoapods here.
In case you want to caching support, Kingfisher comes first. Caching means you will need to make Request to server only for the first time or if something changed. It will save your resources etc. All you need to do is importing Kingfisher module in ViewController then it automatically extend UIImageView capability with Kingfisher extension kf
Here is the sample code
//ViewController.swift
import Kingfisher
// Your cellForRowAt
...
if let url = URL(string: imgURLArray[indexPath.row]) {
cell.imgView.kf.setImage(with: url)
}
...
Kingfisher will perform caching strategy under the hood so you can focus on the app.
In order to put default image which will be very usefull to inform user about the image, (user expects there is something in blank white box -- UIImageView while image is nil -- in their screen isn't?) Kingfisher helps you with placeholder
let defaultImage = UIImage(named: "default_img")!
cell.imgView.kf.setImage(with: url, placeholder: defaultImage)
Get the complete insight about Kingfisher here Kingfisher's Github Page
This question already has answers here:
Loading/Downloading image from URL on Swift
(39 answers)
Closed 4 years ago.
still learning programming and have a question.
im trying to download image from url and put it in cells, ive successfully done it with text but not with images.
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return posts.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCell(withIdentifier:"searchCell", for: indexPath)
as! CustomTableViewCell
cell.titleField?.text = posts[indexPath.row].caption
cell.descriptionField?.text = posts[indexPath.row].description
cell.tagsField?.text = posts[indexPath.row].tags
let photoUrl = posts[indexPath.row].photoUrl
cell.SearchImage.image = ...
//url is stored in photoUrl
return cell
}
}
Use a pod to make it easy
use SDWebImage
then use it like this :
import SDWebImage
cell.SearchImage.sd_setImage(with: photoUrl, placeholderImage: nil)
I recommend you to use this third party library.
Kingfisher handles everything for you.
A lightweight, pure-Swift library for downloading and caching images from the web.
If you don't know how to install it, have a look at Cocoapods, it's pretty straightforward.
Anyway, here is the only line of code needed to set an image from your URL with Kingfisher :
cell.SearchImage.kf.setImage(with: photoUrl)
By the way, you should follow the Swift convention described here in order to make your code easier to read and "universal".
Have a look : Naming Convention
On swift3 You can use Alamofire (https://cocoapods.org/) for that.
Step 1:
Integrate using pods.
pod 'Alamofire', '~> 4.7'
pod 'AlamofireImage', '~> 3.3'
Step 2:
import AlamofireImage
import Alamofire
Step 3:
Alamofire.request("https:// YOUR URL").responseImage { response in
if let image = response.result.value {
print("image downloaded: \(image)")
self.myImageview.image = image
}
}
Or you can try this -
let url = URL(string: "https:// YOUR URL")
let task = URLSession.shared.dataTask(with: url!) { data, response, error in
guard let data = data, error == nil else { return }
DispatchQueue.main.async()
{
self.imageView.image = UIImage(data: data)
}
}
task.resume()
Note: Please no libraries. This is important for me to learn. Also, there are a variety of answers on this but none that I found solves the issue nicely. Please don't mark as duplicate. Thanks in advance!
The problem I have is that if you scroll really fast in the table, you will see old images and flickering.
The solution from the questions I read is to cancel the URLSession
data request. But I do not know how to do that at the correct place
and time. There might be other solutions but not sure.
This is what I have so far:
Image cache class
class Cache {
static let shared = Cache()
private let cache = NSCache<NSString, UIImage>()
var task = URLSessionDataTask()
var session = URLSession.shared
func imageFor(url: URL, completionHandler: #escaping (image: Image? error: Error?) -> Void) {
if let imageInCache = self.cache.object(forKey: url.absoluteString as NSString) {
completionHandler(image: imageInCache, error: nil)
return
}
self.task = self.session.dataTask(with: url) { data, response, error in
if let error = error {
completionHandler(image: nil, error: Error)
return
}
let image = UIImage(data: data!)
self.cache.setObject(image, forKey: url.absoluteString as NSString)
completionHandler(image: image, error: nil)
}
self.task.resume()
}
}
Usage
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
let myImage = images[indexPath.row]
if let imageURL = URL(string: myImage.urlString) {
photoImageView.setImage(from: imageURL)
}
return cell
}
Any thoughts?
Swift 3:
Flickering can be avoided by this way:
Use the following code in public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
cell.photoImageView.image = nil //or keep any placeholder here
cell.tag = indexPath.row
let task = URLSession.shared.dataTask(with: imageURL!) { data, response, error in
guard let data = data, error == nil else { return }
DispatchQueue.main.async() {
if cell.tag == indexPath.row{
cell.photoImageView.image = UIImage(data: data)
}
}
}
task.resume()
By checking cell.tag == indexPath.row, we are assuring that the imageview whose image we are changing, is the same row for which the image is meant to be. Hope it helps!
A couple of issues:
One possible source of flickering is that while you're updating the image asynchronously, you really want to clear the image view first, so you don't see images for prior row of reused/dequeued table view cell. Make sure to set the image view's image to nil before initiating the asynchronous image retrieval. Or, perhaps combine that with "placeholder" logic that you'll see in lots of UIImageView sync image retrieval categories.
For example:
extension UIImageView {
func setImage(from url: URL, placeholder: UIImage? = nil) {
image = placeholder // use placeholder (or if `nil`, remove any old image, before initiating asynchronous retrieval
ImageCache.shared.image(for: url) { [weak self] result in
switch result {
case .success(let image):
self?.image = image
case .failure:
break
}
}
}
}
The other issue is that if you scroll very quickly, the reused image view may have an old image retrieval request still in progress. You really should, when you call your UIImageView category's async retrieval method, you should cancel and prior request associated with that cell.
The trick here is that if you're doing this in a UIImageView extension, you can't just create new stored property to keep track of the old request. So you'd often use "associated values" to keep track of prior requests.
I am new in IOS development using Swift. I created 1 UITableView and displaying images after downloading data. But it is not smooth and some time images are displaying in wrong place when i am scrolling.
I am using AlamofireImage library for image downloading and displaying. Is there any fast library?
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:HomePageCell = tableView.dequeueReusableCell(withIdentifier: "HomePage", for: indexPath) as! HomePageCell
cell.configure( homeData[0], row: indexPath, screenSize: screenSize,
hometableview: self.homeTableView);
return cell
}
import UIKit
import Alamofire
import AlamofireImage
class HomePageCell: UITableViewCell {
#IBOutlet weak var bannerImage: UIImageView!
func configure(_ homeData: HomeRequest, row: IndexPath, screenSize: CGRect, hometableview: UITableView) {
let callData = homeData.banner_lead_stories[(row as NSIndexPath).row]
let url = Constants.TEMP_IMAGE_API_URL + callData.lead_story[0].bg_image_mobile;
if( !callData.lead_story[0].bg_image_mobile.isEmpty ) {
if bannerImage?.image == nil {
let range = url.range(of: "?", options: .backwards)?.lowerBound
let u = url.substring(to: range!)
Alamofire.request(u).responseImage { response in
debugPrint(response)
//print(response.request)
// print(response.response)
// debugPrint(response.result)
if let image = response.result.value {
// print("image downloaded: \(image)")
self.bannerImage.image = image;
self.bannerImage.frame = CGRect(x: 0, y: 0, width: Int(screenSize.width), height: Int(screenSize.width/1.4))
}
}
}
} else {
self.bannerImage.image = nil;
}
}
}
It can be not smooth, because you need to cache your images and make a downloading process not in main thread(read about GCD).
For caching you can go two ways (atleast):
1) Make your own array of images where they will be cached
2) Use KingFisher for example click. It will cache your images.
For example:
yourImageView.kf.setImage(with: URL) // next time, when you will use image with this URL, it will be taken from cache.
Hope it helps
You can use SDWebImage for downloading the image array and add a placeholder image for the time being in imageView. this is function
public func sd_setImageWithURL(url: NSURL!, placeholderImage placeholder: UIImage!)
and it is as easy to use as
myImageView.sd_setImageWithURL(NSURL(string:image), placeholderImage:UIImage(named:"qwerty"))
make sure to reset you imageView in tableView delegate cellforRowAtIndexpath method by setting imageview image to nil
myImageView.image = nil
//now set image in imageView
myImageView.sd_setImageWithURL(NSURL(string:image), placeholderImage:UIImage(named:"qwerty"))
this avoids the image duplicating and weird behave of images as imageview of every cell is being reset before reusing.
Github link -> https://github.com/rs/SDWebImage
You have to use multithreading.Only UI is set in main thread, downloading image in background is in another thread.By this way you can solve your problem.
Try SDWebImage library it will save images in catch automatically and your tableView will work smoothly.
Github link -> https://github.com/rs/SDWebImage
Install pod:
platform :ios, '7.0'
pod 'SDWebImage', '~>3.8'
Just import SDWebImage like:
#import SDWebImage
And use like this:
imageView.sd_setImage(with: URL(string: "http://www.example.com/path/to/image.jpg"), placeholderImage: UIImage(named: "placeholder.png"))
I used it in many live projects and it works like a charm :)
Use this extension to cache your images, and also don't forget to update any UI on the main thread.
let imageCache = NSCache<NSString, UIImage>()
extension UIImageView {
func loadImageUsingCacheWithURLString(_ URLString: String, placeHolder: UIImage?) {
self.image = nil
if let cachedImage = imageCache.object(forKey: NSString(string: URLString)) {
self.image = cachedImage
return
}
if let url = URL(string: URLString) {
URLSession.shared.dataTask(with: url, completionHandler: { (data, response, error) in
//print("RESPONSE FROM API: \(response)")
if error != nil {
print("ERROR LOADING IMAGES FROM URL: \(error)")
DispatchQueue.main.async {
self.image = placeHolder
}
return
}
DispatchQueue.main.async {
if let data = data {
if let downloadedImage = UIImage(data: data) {
imageCache.setObject(downloadedImage, forKey: NSString(string: URLString))
self.image = downloadedImage
}
}
}
}).resume()
}
}
}
I'm new in swift programming and I have searched a lot about storing images with NSCache using Swift.
What I have done so far is that I'm getting ids and imageNames with JSON and I have my data in array and I was able to display image in cells with no problem. Now I want to cache images.
This is the code that I have written:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as TableViewCell
//cell.imageView
nameID = self.hoge[indexPath.row]["cars"]["carid"].string!
cell.nameLabel.text = nameID
if let imageFileURL = imageCache.objectForKey(self.hoge[indexPath.row]["cars"]["carid"].intValue) as? NSURL {
println("Get image from cache")
} else {
imageName = self.hoge[indexPath.row]["cars"]["pic_name"].string!
// If the image does not exist, we need to download it
var imgURL: NSURL = NSURL(string: "http://192.168.1.35/car/uploads/" + imageName )!
var image:UIImage = UIImage(named: "pen")!
// Download an NSData representation of the image at the URL
let request: NSURLRequest = NSURLRequest(URL: imgURL)
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue(), completionHandler: {(response: NSURLResponse!,data: NSData!,error: NSError!) -> Void in
if error == nil {
image = UIImage(data: data)!
cell.viewCell.image = image
}
else {
println("Error: \(error.localizedDescription)")
}
})
}
return cell
}
SO, How can I store and retrieve images with cache?
I recommend you to check this library HanekeSwift (It provides a memory and LRU disk cache for UIImage, NSData, JSON, String or any other type that can be read or written as data), at least to understand how they are dealing with cache, and you can decide to use it or create your own solution.
Using a very easy/simple API:
// Setting a remote image
imageView.hnk_setImageFromURL(url)
// Setting an image manually. Requires you to provide a key.
imageView.hnk_setImage(image, key: key)
Using the cache
let cache = Shared.dataCache
cache.set(value: data, key: "image.png")
// Eventually...
cache.fetch(key: "image.png").onSuccess { data in
// Do something with data
}