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
})
Related
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.
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)
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.
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)
I need to load an image from a url and set it inside an UIImageView; the problem is that I don't know the exact size of the image, then how can I show the image correctly?
Just use the size property of UIImage, for example:
NSURL *url = [NSURL URLWithString:path];
NSData *data = [NSData dataWithContentsOfURL:url];
UIImage *img = [[UIImage alloc] initWithData:data];
CGSize size = img.size;
In swift:
var url = NSURL.URLWithString("http://www.example.com/picture.png")
var data = NSData(contentsOfURL : url)
var image = UIImage(data : data)
image.size // if you need it
In swift regarding using optionals:
var url:NSURL? = NSURL(string: imageString)
var data:NSData? = NSData(contentsOfURL : url!)
var image = UIImage(data : data!)
IN SWIFT 3.0
The main thread must be always remain free so it serves the user interface and user interactions.
class ViewController: UIViewController {
#IBOutlet weak var imageView: UIImageView!
private func fetchImage() {
let imageURL = URL(string: "https://i.stack.imgur.com/9z6nS.png")
var image: UIImage?
if let url = imageURL {
//All network operations has to run on different thread(not on main thread).
DispatchQueue.global(qos: .userInitiated).async {
let imageData = NSData(contentsOf: url)
//All UI operations has to run on main thread.
DispatchQueue.main.async {
if imageData != nil {
image = UIImage(data: imageData as! Data)
self.imageView.image = image
self.imageView.sizeToFit()
} else {
image = nil
}
}
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
fetchImage()
}
}
To download Asynchronous image with Kingfisher library you can follow this step,url :https://github.com/onevcat/Kingfisher:
func imageFromUrl(_ urlString: String) {
if let url = URL(string: urlString) {
ImageDownloader.default.downloadImage(with: url, options: [], progressBlock: nil) {
(image, error, url, data) in
DispatchQueue.main.async {
self.imageView.image = image
}
}
}
}
You can also download image with default URLSession.shared.dataTask
func imageFromUrl(_ urlString: String) {
if let url = URL(string: urlString) {
let request = URLRequest(url: url)
URLSession.shared.dataTask(with: request) {(data,response,error) in
if let imageData = data as Data? {
if let img = UIImage(data: imageData){
DispatchQueue.main.async {
self.imageView.image = img
}
}
}
}
}
}
Swift safe code version:
private func loadImage(from url:URL) -> UIImage? {
let imageData: Data
do {
imageData = try Data(contentsOf: url)
} catch {
return nil
}
return UIImage(data: imageData)
}
private func loadImage(from urlString:String) -> UIImage? {
guard let url = URL(string: urlString) else {
return nil
}
return self.loadImage(from: url)
}
Keep in mind that this code blocks the main thread, so you should run it on a background thread. For example:
DispatchQueue.global().async {
let image = UIImage(fromFile: "http://xpto.com/image.png")
// And then you should update UI on main thread.
// If you have an UIImageView outlet you can update its image this way:
DispatchQueue.main.async {
imageView.image = image
imageView.contentMode = .scaleAspectFit
}
}
SWIFT 5.0 + fetch on background
private func fetchImage(_ photoURL: URL?) {
guard let imageURL = photoURL else { return }
DispatchQueue.global(qos: .userInitiated).async {
do{
let imageData: Data = try Data(contentsOf: imageURL)
DispatchQueue.main.async {
let image = UIImage(data: imageData)
self.userImageView.image = image
self.userImageView.sizeToFit()
self.tableView.reloadData()
}
}catch{
print("Unable to load data: \(error)")
}
}
}
One common mistake in displaying an image downloaded from json or a url is the problem of queues. ALL UI-related things need to be done in the main queue, so if you forgot this, even perfect code (above answers are good) won't display your image on occasion. To call the mainQueue, use code like this, and note that calling main queue might need to be done seperately in the called imageDisplay function:
dispatch_async(dispatch_get_main_queue(), ^{
self.nameLabel.text = self.pokemon.name;
[self displayImage]; //CALLS FUNCTION
self.abilitiesTextView.text = #"loves SwiftUI";
});
- (void)displayImage {
NSString *imageURLString = [NSString stringWithFormat: self.pokemon.sprite];
NSData *imageData = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:imageURLString]];
dispatch_async(dispatch_get_main_queue(), ^{
self.spriteImageView.image = [UIImage imageWithData: imageData];
});
// NOTE: important for newer versions of XCode/Obj-C...
//[imageData release]; With ARC ("automated release..."), release method is forbidden, it's already done for you.
}
If you prefer you can even move it to an UIImage extension:
extension UIImage {
//NOTE: This is not thread safe, please run it on a background thread.
convenience init?(fromFile filePath:String) {
guard let url = URL(string: filePath) else {
return nil
}
self.init(fromURL: url)
}
//NOTE: This is not thread safe, please run it on a background thread.
convenience init?(fromURL url:URL) {
let imageData: Data
do {
imageData = try Data(contentsOf: url)
} catch {
return nil
}
self.init(data: imageData)
}
}