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)
Related
I am trying to show an image into my table cell view from an API. But it has given a partial link there, as a result, I am getting NSURL connection error code -1002.
Here is my API link: https://api.opendota.com/api/heroStats
I am trying to parse "icon" among them:
"img": "/apps/dota2/images/heroes/antimage_full.png?",
"icon": "/apps/dota2/images/heroes/antimage_icon.png",
My code:
// Generating imageview
if let imageURL = URL(string: heroes[indexPath.row].icon){
print (imageURL)
DispatchQueue.global().async {
let data = try? Data (contentsOf: imageURL)
if let data = data {
let image = UIImage(data: data)
DispatchQueue.main.async {
cell.charIcon.image = image
} //end of 2nd dispatch
}//end of if
}//end of 1st dispatch
}// end of imageURL
How can I solve this problem? Any easy way for swift 4?
You can get the url components of your api link and use your icon "partial link" to set the path property of the URL components. After that you just need to get the resulting url of the url components:
let apiLink = "https://api.opendota.com/api/heroStats"
let apiURL = URL(string: apiLink)!
if var urlComponents = URLComponents(url: apiURL, resolvingAgainstBaseURL: false) {
let iconString = "/apps/dota2/images/heroes/antimage_icon.png"
urlComponents.path = iconString
if let iconURL = urlComponents.url {
print(iconURL.absoluteString)
}
}
This will print
https://api.opendota.com/apps/dota2/images/heroes/antimage_icon.png
You can create a custom method to return a new URL based on the new path string as follow:
extension URL {
var urlComponents: URLComponents? {
return URLComponents(url: self, resolvingAgainstBaseURL: false)
}
func bySettingNew(path: String) -> URL? {
guard var urlComponents = urlComponents else { return nil }
urlComponents.path = path
return urlComponents.url
}
}
let apiLink = "https://api.opendota.com/api/heroStats"
let apiURL = URL(string: apiLink)!
let iconString = "/apps/dota2/images/heroes/antimage_icon.png"
if let iconURL = apiURL.bySettingNew(path: iconString) {
print(iconURL.absoluteString)
}
You can also add this helper to your project to make it easier for you to download an image asynchronously into your image view:
extension UIImageView {
func downloaded(from url: URL, contentMode mode: UIView.ContentMode = .scaleAspectFit) {
URLSession.shared.dataTask(with: url) { data, response, error in
guard
let httpURLResponse = response as? HTTPURLResponse, httpURLResponse.statusCode == 200,
let mimeType = response?.mimeType, mimeType.hasPrefix("image"),
let data = data, error == nil,
let image = UIImage(data: data)
else { return }
DispatchQueue.main.async() { [weak self] in
self?.contentMode = mode
self?.image = image
}
}.resume()
}
}
if let imageURL = apiURL.bySettingNew(path: heroes[indexPath.row].icon) {
cell.charIcon.downloaded(from: imageURL)
}
This question already has answers here:
Loading/Downloading image from URL on Swift
(39 answers)
Closed 3 years ago.
I am trying to load image from url in my ios app swift. I have written following code.
let imageURL = minHost + "\(userData["profileImage"])"
let url = URL(string: imageURL)!
let imageData = try? Data(contentsOf: url)
profileImage.image = UIImage(data: imageData!)
Now imageURL is having proper url, but imageData receives nil and because of this, last line through an error Fatal error: Unexpectedly found nil while unwrapping an Optional value
Instead of fetching image using Data(contentsOf:) method, use URLSession to perform network calls.
let imageURL = minHost + "\(userData["profileImage"])"
if let url = URL(string: imageURL) {
URLSession.shared.dataTask(with: url) {[weak self] (data, urlResponse, error) in
if let data = data {
DispatchQueue.main.async {
self?.profileImage.image = UIImage(data: imageData)
}
}
}.resume()
}
Important Note: Avoid using forced unwrapping (!) unnecessarily. It might result in unwanted app crashes. Instead use guard or if-let to unwrap optionals.
Try this at Playground.
Loading image from the URL takes some time, and need to be executed at another Thread, different from the main thread.
import UIKit
let url = URL(string: "https://cdn.arstechnica.net/wp-content/uploads/2018/06/macOS-Mojave-Dynamic-Wallpaper-transition.jpg")!
var image = UIImage()
DispatchQueue.global().async {
if let data = try? Data(contentsOf: url) {
DispatchQueue.main.async {
image = UIImage(data: data)!
}
}
}
image
you can try like this:
let url = URL(string: "image url here")
if url != nil {
DispatchQueue.global().async { [weak self] in
if let data = try? Data(contentsOf: url!) {
if let image = UIImage(data: data) {
DispatchQueue.main.async {
self.profileImage.image = image
}
}
}
}
}
Try This
let url = URL(string:imageURL)
if let data = try? Data(contentsOf: url!)
{
profileImage.image = UIImage(data: data, scale: 1.0)!
}
Never do the downloading task on main thread. if you do, you will not able to access components in current visible screens properly. It should be always on the background thread.
if let url = URL(string: "https://....") {
DispatchQueue.global(qos: .background).async {
if let data = try? Data(contentsOf: url) {
if let image = UIImage(data: data) {
DispatchQueue.main.async {
self.profileImage.image = image
}
}
}
}
}
In my project, I show a UITableView, which currently has text describing a show's name and genre loading from a remote JSON file.
That all works. What I want next is to use the URL from the JSON file and load a thumbnail next to each show.
Using a tutorial, I have added a function to download the remote image with a print to test if it's successful.
if let shows_list = json as? NSArray
{
for i in 0 ..< data_list.count
{
if let shows_obj = shows_list[i] as? NSDictionary
{
let show_name = shows_obj["show"] as? String
let show_genre = shows_obj["genre"] as? String
let show_image = shows_obj["thumbnail"] as? String
TableData.append(show_name! + " | " + show_genre!)
let testPictureURL = URL(string: show_image!)!
let session = URLSession(configuration: .default)
// Here's the download task where I'm grabbing the image
let downloadPicTask = session.dataTask(with: testPictureURL) { (data, response, error) in
// The download has finished.
if let e = error {
print("Error downloading cat picture: \(e)")
} else {
// No errors found.
if let res = response as? HTTPURLResponse {
print("Downloaded picture with response code \(res.statusCode)")
if let imageData = data {
// Now I know I have data, so I think I can use UIImage to convert it into an image
let image = UIImage(data: imageData)
} else {
print("Couldn't get image: Image is nil")
}
} else {
print("Couldn't get response code for some reason")
}
}
}
downloadPicTask.resume()
}
There are three items in the JSON array, and I get three printed statements that the picture was download: but the image does not appear.
My theory: since this is a table, maybe I have to add this as an accessory, but there isn't an image accessory subclass.
I am new to Swift -- do you have any ideas about how I should append this uploaded image to the table.
This is probably being caused by the asynchronous behavior of URLSession so when the requested image returns the view is already loaded.
To solve that, you can use a callback, for instance:
func myFunction(completion: (returnedImage -> UIIMage) -> Void){
//...
let downloadPicTask = session.dataTask(with: testPictureURL) { (data, response, error) in
//...
let image = UIImage(data: imageData)
completion(returnedImage: image)
//...
}
downloadPicTask.resume()
}
}
By using a callback, let's say that you have a method called myFunction(completion:), so now when you call the method you can handle whatever comes back from completion:
myFunction { (image) in
DispatchQueue.main.async { cell.imageView.image = image }
}
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'm trying to display and save images with Swift. On first hit, it shows the remote image on imageview, on second hit it shows blank imageview instead of it should be local image which saved on first hit.
var paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] as String
var imagePath = paths.stringByAppendingPathComponent("images/\(id)/logo.jpg" )
var checkImage = NSFileManager.defaultManager()
if (checkImage.fileExistsAtPath(imagePath)) {
let getImage = UIImage(contentsOfFile: imagePath)
self.image?.image = getImage
} else {
dispatch_async(dispatch_get_main_queue()) {
let getImage = UIImage(data: NSData(contentsOfURL: NSURL(string: remoteImage)))
UIImageJPEGRepresentation(getImage, 100).writeToFile(imagePath, atomically: true)
self.image?.image = getImage
}
}
Edit: This one worked for me.
var paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] as String
var dirPath = paths.stringByAppendingPathComponent("images/\(id)" )
var imagePath = paths.stringByAppendingPathComponent("images/\(id)/logo.jpg" )
var checkImage = NSFileManager.defaultManager()
if (checkImage.fileExistsAtPath(imagePath)) {
let getImage = UIImage(contentsOfFile: imagePath)
self.image?.image = getImage
} else {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0)) {
checkImage.createDirectoryAtPath(dirPath, withIntermediateDirectories: true, attributes: nil, error: nil)
let getImage = UIImage(data: NSData(contentsOfURL: NSURL(string: remoteImage)))
UIImageJPEGRepresentation(getImage, 100).writeToFile(imagePath, atomically: true)
dispatch_async(dispatch_get_main_queue()) {
self.image?.image = getImage
return
}
}
}
To answer your main question, you're calling the wrong UIImage initializer. You should be calling UIImage(contentsOfFile: imagePath) in swift 2 and UIImage(contentsOf: imagePath) in swift 3.
Additionally, it looks like you're trying to do your remote fetch in the background with dispatch_async (or DispatchQueue in swift 3), but you're passing it the main queue, so you're actually blocking the main/UI thread with that. You should dispatch it to one of the background queues instead and then dispatch back to the main queue when you actually set the image in your UI:
Swift 3 :
DispatchQueue.global(qos: DispatchQoS.background.qosClass).async {
do {
let data = try Data(contentsOf: URL(string: self.remoteImage)!)
let getImage = UIImage(data: data)
try UIImageJPEGRepresentation(getImage!, 100)?.write(to: imagePath)
DispatchQueue.main.async {
self.image?.image = getImage
return
}
}
catch {
return
}
}
Swift 2 :
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0)) {
let getImage = UIImage(data: NSData(contentsOfURL: NSURL(string: self.remoteImage)))
UIImageJPEGRepresentation(getImage, 100).writeToFile(imagePath, atomically: true)
dispatch_async(dispatch_get_main_queue()) {
self.image?.image = getImage
return
}
}
#Rob's answer re: fetching your remote image and saving it is really the best way to do this.
Your code that dispatches NSData(contentsOfURL:) (now known as Data(contentsOf:)) to the main queue. If you're going to use that synchronous method to request remote image, you should do this on a background queue.
Also, you are taking the NSData, converting it to a UIImage, and then converting it back to a NSData using UIImageJPEGRepresentation. Don't round-trip it though UIImageJPEGRepresentation as you will alter the original payload and will change the size of the asset. Just just confirm that the data contained an image, but then write that original NSData
Thus, in Swift 3, you probably want to do something like:
DispatchQueue.global().async {
do {
let data = try Data(contentsOf: URL(string: urlString)!)
if let image = UIImage(data: data) {
try data.write(to: fileURL)
DispatchQueue.main.async {
self.imageView?.image = image
}
}
} catch {
print(error)
}
}
Even better, you should use NSURLSession because you can better diagnose problems, it's cancelable, etc. (And don't use the deprecated NSURLConnection.) I'd also check the statusCode of the response. For example:
func requestImage(_ url: URL, fileURL: URL) {
let task = URLSession.shared.dataTask(with: url) { data, response, error in
// check for fundamental network issues (e.g. no internet, etc.)
guard let data = data, error == nil else {
print("dataTask error: \(error?.localizedDescription ?? "Unknown error")")
return
}
// make sure web server returned 200 status code (and not 404 for bad URL or whatever)
guard let httpResponse = response as? HTTPURLResponse, 200 ..< 300 ~= httpResponse.statusCode else {
print("Error; Text of response = \(String(data: data, encoding: .utf8) ?? "(Cannot display)")")
return
}
// save image and update UI
if let image = UIImage(data: data) {
do {
// add directory if it doesn't exist
let directory = fileURL.deletingLastPathComponent()
try? FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true)
// save file
try data.write(to: fileURL, options: .atomic)
} catch let fileError {
print(fileError)
}
DispatchQueue.main.async {
print("image = \(image)")
self.imageView?.image = image
}
}
}
task.resume()
}
Note, the just-in-time creation of the folder is only necessary if you haven't created it already. Personally, when I build the original path, I'd create the folder there rather than in the completion handler, but you can do this any way you want. Just make sure the folder exists before you write the file.
Regardless, hopefully this illustrates the main points, namely that you should save the original asset and that you should do this in the background.
For Swift 2 renditions, see previous revision of this answer.