I have a table view that loads images from s3 bucket and set some data with the images in my cell.
I call my cell at
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellDish:DishTableViewCell = tableView.dequeueReusableCell(withIdentifier: "DishCell", for: indexPath) as! DishTableViewCell
cellDish.setDish(dish: brain.listOfDishes[indexPath.row])
return cellDish }
in my tableviewcell I have a func called setDish :
func setDish (dish: Dish)
{
var StringPrice = dish.DishPrice
StringPrice.append("$")
self.la_name.text = dish.DishName
self.la_price.text = StringPrice
self.la_des.text = dish.DishDes
self.downloadData(dish: dish, completion: { success in
guard success else { return }
DispatchQueue.main.async {
self.dish_image.image = UIImage(data: dish.DishData!)!
}
})
}
func downloadData(dish:Dish,completion: #escaping (Bool) -> Void) {
let transferUtility = AWSS3TransferUtility.default()
let expression = AWSS3TransferUtilityDownloadExpression()
let s3Bucket = "<my bucket name>"
transferUtility.downloadData(fromBucket: s3Bucket, key: dish.DishImage, expression: expression) {(task, url, data, error) in
if error != nil {print(error)
completion(false)
}
else {
dish.DishData = data!
}
completion(true)
}
}
I want it to show me the dish data without the image until the image is downloaded and then show it to me as well (I want it to be not on the main thread of course ).
I'm not sure why but right now all the cells download their images and only then everything loads up together.
Swift 3 Updated Code :
Load url asynchronous will update automatically
extension UIImageView
{
public func imageFromServerURL(urlString: String)
{
URLSession.shared.dataTask(with: NSURL(string: urlString)! as URL, completionHandler: { (data, response, error) -> Void in
if error != nil {
print(error)
return
}
DispatchQueue.main.async(execute: { () -> Void in
let image = UIImage(data: data!)
self.image = image
})
}).resume()
}}
Swift 2.2 Code :
extension UIImageView {
public func imageFromServerURL(urlString: String) {
NSURLSession.sharedSession().dataTaskWithURL(NSURL(string: urlString)!, completionHandler: { (data, response, error) -> Void in
if error != nil {
print(error)
return
}
dispatch_async(dispatch_get_main_queue(), { () -> Void in
let image = UIImage(data: data!)
self.image = image
})
}).resume()
}}
Related
I am making an iOS movie app using UIKIT and Swift and I wanted to display the movies in a collectionview but there's some kind of glitch and there are some kind of traingle being diplayed instead of the movie.
Could someone please help? Thanks
moviesViewController.swift
//
// FirstViewController.swift
// PopcornTime
//
// Created by Maxime Ruys on 15/03/2020.
// Copyright © 2020 Pixel-Developers. All rights reserved.
//
import UIKit
import SwiftyJSON
class MoviesViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate {
#IBOutlet weak var moviesCollectionView: UICollectionView!
var movies = [Movie]()
override func viewDidLoad() {
super.viewDidLoad()
self.moviesCollectionView.delegate = self
self.moviesCollectionView.dataSource = self
fetchMovies()
}
func fetchMovies(){
self.movies = []
let url = URL(string: "API_URL_HERE")
guard let requestUrl = url else { fatalError() }
var request = URLRequest(url: requestUrl)
request.httpMethod = "GET"
request.httpBody = "".data(using: String.Encoding.utf8)
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
if (error != nil) {
print(error)
} else {
if let data = data, let dataString = String(data: data, encoding: .utf8) {
do {
if let dataFromString = dataString.data(using: .utf8, allowLossyConversion: false) {
let json = try JSON(data: dataFromString)
for(_, movie) in json{
var imgs = [String]()
imgs.append(movie["images"]["banner"].stringValue)
imgs.append(movie["images"]["poster"].stringValue)
imgs.append(movie["images"]["fanart"].stringValue)
self.movies.append(Movie(id: movie["_id"].stringValue, title: movie["title"].stringValue, desc: movie["synopsis"].stringValue, playTime: movie["runtime"].stringValue, imgs: imgs))
}
}
} catch {
print(error)
}
DispatchQueue.main.async {
self.moviesCollectionView.reloadData()
}
}
}
}
task.resume()
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.movies.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "MoviesRowController",
for: indexPath) as! MoviesRowController
cell.moviesRowImg.image = downloadImage(from: URL(string: movies[indexPath.row].imgs[0])!)
cell.moviesTitleLbl.text = movies[indexPath.row].title
return cell
}
func downloadImage(from url: URL) -> UIImage{
getData(from: url) { data, response, error in
guard let data = data, error == nil else { return }
DispatchQueue.main.async() {
return UIImage(data: data)
}
}
return UIImage(named: "test")!
}
func getData(from url: URL, completion: #escaping (Data?, URLResponse?, Error?) -> ()) {
URLSession.shared.dataTask(with: url, completionHandler: completion).resume()
}
}
collectionview glitch image:
collectionview properties:
colletionview cell properties:
You can't assign a value that's asynchronous , so return here is nil
func downloadImage(from url: URL) -> UIImage{
getData(from: url) { data, response, error in
guard let data = data, error == nil else { return }
DispatchQueue.main.async() {
return UIImage(data: data)
}
}
return UIImage(named: "test")
}
use SDWebImage
You can do introspection like below.
if movies[indexPath.row].imgs.count>0, let url = URL(string: movies[indexPath.row].imgs[0]!) {
}
If you wish to Cache the images to avoid multiple requests, use below extension.
let imageCache = NSCache<NSString, UIImage>()
extension UIImageView {
func imageFromServerURL(_ 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()
}
}}
Your cell is not registered either:
self.collectionView.register(UICollectionViewCell.self, forCellReuseIdentifier: "movieCell")
I want to get a UIImage and title from my object so I make a generic result with enum.
but I have an error while I try to take my result in completionHandler and an error while I called my func fetchDataResult(for video: Video, completion: #escaping (Results<(UIImage?, String?)>) -> ()) in my controller. This is my code.
Member 'success' in 'Results<(UIImage?, String?)>' produces result of type 'Results', but context expects 'Results<(UIImage?, String?)>'
enum Results<Value> {
case success(Value)
case failure(Error)
}
class VideoStore {
func fetchDataResult(for video: Video, completion: #escaping (Results<(UIImage?, String?)>) -> ()) {
guard let videoKey = video.videoID else {
preconditionFailure("Video expected to have a video id")
}
if let image = imageStore.image(forKey: videoKey) {
OperationQueue.main.addOperation {
completion(.success((image, nil)))
}
return
}
guard let videoURL = video.url else {
preconditionFailure("Video expected to have video url")
}
if let videoTitle = video.title {
OperationQueue.main.addOperation {
completion(.success((nil, videoTitle)))
}
}
let request = URLRequest(url: videoURL)
let task = sessions.dataTask(with: request) { (data, response, error) in
let result = self.processDataRequest(data: data, error: error)
if case let .success(image) = result {
self.imageStore.setImage(image, forKey: videoKey)
}
OperationQueue.main.addOperation {
completion(.success((result, nil)))
}
}
task.resume()
}
private func processDataRequest(data: Data?, error: Error?) -> Results<UIImage> {
guard
let imageData = data,
let image = UIImage(data: imageData) else {
if data == nil {
return .failure(error!)
} else {
return .failure(ThumbnailError.thumbnailCreationError)
}
}
return .success(image)
}
}
This is my VideoController that have an error
Cannot assign value of type '(UIImage?, String?)' to type 'UIImage?'
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
let video = videoDataSource.videos[indexPath.row]
videoStore.fetchDataResult(for: video) { (results) in
guard
let videoIndex = self.videoDataSource.videos.firstIndex(of: video),
case let .success(image) = results else { return }
let photoIndexPath = IndexPath(item: videoIndex, section: 0)
if let cell = tableView.cellForRow(at: photoIndexPath) as? VideoCell {
cell.videoImgView.image = image
}
}
}
The results in the following line:
videoStore.fetchDataResult(for: video) { (results) in
has a type (UIImage?, String?).
The completion handler for your fetchDataResult gets the type Results<(UIImage?, String?)> as you defined, which makes the case success(Value) to case success((UIImage?, String?)).
If you only need UIImage? from the value, you need to do
guard
let videoIndex = self.videoDataSource.videos.firstIndex(of: video),
case let .success(image, _) = results else { return }
Note that Swift has its own Results type since Swift 5.
I need to print image in my next view controller after comparing ID of a table containing user details after comparing the ID I am successfully getting the name but the respective image is unable to fetch
if the user has posted anything then I am getting name for particular posted job now what I want is image of from respective user (that image which user uploaded while registration), (which identifies the posted job is posted via which user).
Below is my code:
func getJOBData()
{
let jobUrl = URL(string: "http://172.16.1.22/Get-Job-API/get-jobs/")
URLSession.shared.dataTask(with: jobUrl!) { (data, response, error) in
do
{
if error == nil
{
self.jobArray = try JSONDecoder().decode([JobData].self, from: data!)
for mainJobArr in self.jobArray
{
DispatchQueue.main.async {
self.jobPostTblView.reloadData()
}
}
print("Job Data****\(self.jobArray)")
}
}
catch
{
// print("my data=\(self.mainCellData)")
print("Error in get JSON Data\(error)")
}
}.resume()
}
numberOfRowsInSection
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return jobFilteredArray.count
}
cellForRowAtIndexPath Method
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = Bundle.main.loadNibNamed("JobTableViewCell", owner: self, options: nil)?.first as! JobTableViewCell
let data = jobArray[indexPath.row]
cell.jobTitle.text = data.job_desig
cell.expDetails.text = data.job_exp_to
cell.locationDetails.text = data.job_location
cell.dateDetails.text = data.job_skills
cell.companyName.text = companyArray.first { $0.company_id == data.company_id }?.company_name
return cell
}
didSelectRowAtIndexPath
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath){
let selectedCell:UITableViewCell = tableView.cellForRow(at: indexPath)!
selectedCell.contentView.backgroundColor = UIColor.white
let rows = indexPath.row
print("Rows=\(rows)")
let jobDetail = WorkerJobDetailsViewController(nibName: "WorkerJobDetailsViewController", bundle: nil)
let jdata = jobFilteredArray[indexPath.row]
jobDetail.gender = jobArray[indexPath.row].job_emp_gender
jobDetail.location = jobArray[indexPath.row].job_location
jobDetail.companyName = (companyArray.first { $0.company_id == jdata.company_id }?.company_name)!
jobDetail.profile = jobImgPath
jobImgPath = (companyArray.first { $0.company_id == jdata.company_id }?.company_logo)!
jobDetail.skills = jobArray[indexPath.row].job_skills
jobDetail.descriptionValue = jobArray[indexPath.row].job_desc
jobDetail.jobDesignation = jobArray[indexPath.row].job_desig
self.present(jobDetail, animated: true, completion: nil)
}
Can anyone please help me to fetch images for respective user of posted job??
just use a pod like SDWebImage and fetch the link with the url that you are mapping the problem that you will face is that the link is a local ip and it will not work from a remote network but if the link changes at the json in the future you will be fine
Without pod you can do this
func getDataFromUrl(url: URL, completion: #escaping (Data?, URLResponse?, Error?) -> ()) {
URLSession.shared.dataTask(with: url) { data, response, error in
completion(data, response, error)
}.resume()
}
func downloadImage(url: URL) {
print("Download Started")
getDataFromUrl(url: url) { data, response, error in
guard let data = data, error == nil else { return }
print(response?.suggestedFilename ?? url.lastPathComponent)
print("Download Finished")
DispatchQueue.main.async() {
self.imageView.image = UIImage(data: data)
}
}
}
I run this code as an extension to imageView to load image to cells.the problem is images are loading to cells but all cells image are invisible or hidden till I click on one of them after that all cells images are shown !
extension UIImageView {
func downloadImageFrom(link:String, contentMode: UIViewContentMode) {
URLSession.shared.dataTask( with: NSURL(string:link)! as URL, completionHandler: {
(data, response, error) -> Void in
DispatchQueue.main.async {
self.contentMode = contentMode
if let data = data { self.image = UIImage(data: data) }
}
}).resume()
}
}
and in the cellforRowAt :
cell.imageView?.downloadImageFrom (link)
There is less info posted on question still I can assume following may be issue
-> Your cell has UIImageView and you are downloading image but after image download how you are notifying cell ? ,
-> Since tableview reuse cells and your imageview will also been re used by tableview cell in your code you are just downloading image data and set to UIImageView that will create problem in future
TIP:
1) Use disk or memory caching ,
2) Use placeholder image while your cell is downloading image
3) Use Completion handler (closure) to notify your cell when you have finished downloading
4) You can use third party library which is very ease to use like AlamofireImage or SDWebImage
You can use following code : -
extension UIImageView {
static let cacheData = NSCache<AnyObject, AnyObject>()
func downloadedFrom(link: String, placeHolder:String = "placeholder",isFromCache:Bool = true,isIndicator:Bool = true,isAppendBaseUrl:Bool = true) {
let placeHolderImage = UIImage.init(named: placeHolder);
self.contentMode = UIViewContentMode.scaleAspectFill
self.clipsToBounds = true;
self.image=placeHolderImage;
var strlink = ""
if isAppendBaseUrl
{
strlink = "Your base url" + link
}
else
{
strlink = link
}
let activityIndicator = UIActivityIndicatorView(activityIndicatorStyle: UIActivityIndicatorViewStyle.whiteLarge)
guard let url = URL(string: strlink) else
{
return
}
if isIndicator
{
activityIndicator.center = CGPoint(x: self.bounds.size.width/2, y: self.bounds.size.height/2)
activityIndicator.color = UIColor.white
self.addSubview(activityIndicator)
activityIndicator.startAnimating()
}
if isFromCache
{
if let cachedImage = UIImageView.cacheData.object(forKey: url as AnyObject) as? UIImage
{
if isIndicator
{
activityIndicator.stopAnimating()
}
self.image = cachedImage
}
else
{
self.image = placeHolderImage
let urlStr = strlink
let url = URL(string: urlStr)
let request: URLRequest = URLRequest(url: url!)
URLSession.shared.dataTask(with: request, completionHandler: { (data, response, error) in
if error != nil
{
print(error!)
return
}
DispatchQueue.main.async(execute: { () -> Void in
let image = UIImage(data: data!)
if isIndicator
{
activityIndicator.stopAnimating()
activityIndicator.removeFromSuperview();
}
if (image != nil)
{
self.image = image
UIImageView.cacheData.setObject(image!, forKey: url as AnyObject)
}
})
}).resume()
}
}
else
{
self.image=placeHolderImage;
let urlStr = strlink
let url = URL(string: urlStr)
var request: URLRequest = URLRequest(url: url!)
request.setValue("xyz", forHTTPHeaderField:"DeviceId")
URLSession.shared.dataTask(with: request, completionHandler: { (data, response, error) in
if error != nil
{
print(error!)
return
}
DispatchQueue.main.async(execute: { () -> Void in
let image = UIImage(data: data!)
if isIndicator
{
activityIndicator.stopAnimating()
activityIndicator.removeFromSuperview();
}
if (image != nil)
{
self.image = image
UIImageView.cacheData.setObject(image!, forKey: url as AnyObject)
}
})
}).resume()}
}}
I solved my problem by adding a subview in table cells and hold images
Try this, it works for me. May be forgot to specify a method super.viewDidLoad() or super.viewDidAppear(true)...
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "idFriendCell", for: indexPath) as! FriendCell
cell.fotoFriend.downloadImageFrom(link: "http://foto.jpg")
return cell
}
extension UIImageView {
func downloadImageFrom(link: String) {
URLSession.shared.dataTask(with: URL(string: link)!) { (data, response, error) in
DispatchQueue.main.async {
guard let data = data else { return }
self.image = UIImage(data: data)
}
}.resume()
}
}
So I am making a network request. I parse the JSON to custom Objects. In these objects there are urls which are suppose to bring back images. One of the URL returns an error message (404) so there ins't anything there! How can I set a default image in its place and stop my app from crashing? Here is my code! Thanks
import UIKit
class HomepageCollectionViewController: UICollectionViewController {
var imageCache = NSCache()
var hingeImagesArray = [HingeImage]()
var arrayToHoldConvertedUrlToUIImages = [UIImage]()
var task: NSURLSessionDataTask?
override func viewDidLoad() {
super.viewDidLoad()
// Makes the network call for HingeImages
refreshItems()
}
override func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return 1
}
override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return hingeImagesArray.count
}
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("imageReuseCell", forIndexPath: indexPath) as! ImageCollectionViewCell
let image = hingeImagesArray[indexPath.row]
if let imageURL = image.imageUrl {
if let url = NSURL(string: imageURL) {
//settingImageTpChache
if let myImage = imageCache.objectForKey(image.imageUrl!) as? UIImage {
cell.collectionViewImage.image = myImage
}else {
// Request images asynchronously so the collection view does not slow down/lag
self.task = NSURLSession.sharedSession().dataTaskWithURL(url, completionHandler: { (data, response, error) -> Void in
// Check if there is data returned
guard let data = data else {
return
}
// Create an image object from our data and assign it to cell
if let hingeImage = UIImage(data: data){
//cachedImage
self.imageCache.setObject(hingeImage, forKey: image.imageUrl!)
dispatch_async(dispatch_get_main_queue(), { () -> Void in
cell.collectionViewImage.image = hingeImage
//append converted Images to array so we can send them over to next view - only proble in that the only images converted at the ones you scrool to which is retarted
self.arrayToHoldConvertedUrlToUIImages.append(hingeImage)
print(self.arrayToHoldConvertedUrlToUIImages)
})
}
})
task?.resume()
}
}
}
return cell
}
you can check if error is not nil then set deafult image .
self.task = NSURLSession.sharedSession().dataTaskWithURL(url, completionHandler: { (data, response, error) -> Void in
if error != nil {
cell.collectionViewImage.image = UIImage(named:"default_image")
return
}
...
Try this:
let imageCache = NSCache<AnyObject, AnyObject>()
extension UIImageView {
func loadImageUsingCacheWithUrl(urlString: String) {
self.image = nil
// check for cache
if let cachedImage = imageCache.object(forKey: urlString as AnyObject) as? UIImage {
self.image = cachedImage
return
}
// download image from url
let url = URL(string: urlString)
URLSession.shared.dataTask(with: url!, completionHandler: { (data, response, error) -> Void in
var image:UIImage
if error == nil {
if(UIImage(data: data!) != nil){
image = UIImage(data: data!)!
} else {
image = UIImage(named: "DefaultImage")!
}
} else {
print(error ?? "load image error")
return
}
DispatchQueue.main.async(execute: { () -> Void in
imageCache.setObject(image, forKey: urlString as AnyObject)
self.image = image
})
}).resume()
}
}
The key point is with 404 return message, data task error is still = nil and this time you must check UIImage(data: data!) != nil to prevent a “fatal error: unexpectedly found nil while unwrapping an Optional value”