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.
Related
I am trying to make a very simple app in MVVM and I must be missing something here but I can't figure it out. I have all the error handling in my NewsService class and I print success if all goes right and it receives the data. I get that success every time, the issue is the "print(articles)" are not printing anything at all.
class NewsTableViewModel {
var articles = [Article]() {
didSet {
print(articles)
}
}
func fetchNews() {
NewsService.shared.fetchNews { [weak self] articles in
guard let self = self else { return }
self.articles = articles
print(articles)
}
}
}
class NewsTableVC: UITableViewController, NewsTableViewModelDelegate {
private let reuseIdentifier = "ArticleCell"
private let newsTableVM = NewsTableViewModel()
// var article = [Article]() {
// didSet {
// DispatchQueue.main.async {
// self.tableView.reloadData()
// }
// }
// }
override func viewDidLoad() {
super.viewDidLoad()
newsTableVM.delegate = self
newsTableVM.fetchNews()
updateUI()
}
func updateUI() {
tableView.register(ArticleCell.self, forCellReuseIdentifier: reuseIdentifier)
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// return article.count
return self.newsTableVM.articles.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: reuseIdentifier, for: indexPath) as! ArticleCell
// cell.articleTitleLabel.text = article[indexPath.row].title
// cell.articleDescriptionLabel.text = article[indexPath.row].description
cell.articleTitleLabel.text = newsTableVM.articles[indexPath.row].title
cell.articleDescriptionLabel.text = newsTableVM.articles[indexPath.row].description
return cell
}
}
struct Response: Codable {
let articles: [Article]
}
struct Article: Codable {
let title: String
let description: String
}
class NewsService {
static let shared = NewsService()
func fetchNews(completion: #escaping ([Article]) -> (Void)) {
if let urlString = URL(string: "") {
let task = URLSession.shared.dataTask(with: urlString) { data, response, error in
if let _ = error {
print("error")
return
}
guard let response = response as? HTTPURLResponse, response.statusCode == 200 else { return }
guard let data = data else {
return
}
let decoder = JSONDecoder()
do {
print("success")
let articles = try decoder.decode(Response.self, from: data).articles
completion(articles)
} catch {
return
}
}
task.resume()
}
}
}
In my view controller viewDidLoad, I call NewsTableViewModel().fetchNews(). And here is the entire NewsTableViewModel class. Ignore the double use of print(articles), I'm just trying to figure out where it's going wrong.
you did not cover all the cases, put debug print at:
guard let self = self else {
print("self is nill")
return completion([])
}
and:
guard let response = response as? HTTPURLResponse, response.statusCode == 200 else {
print("error: \(response)")
return completion([])
}
guard let data = data else {
print("error: data is nill")
return completion([])
}
and:
do {
print("success")
let articles = try decoder.decode(Response.self, from: data).articles
completion(articles)
} catch (let error){
print("catch an error: \(error)
completion([])
}
also put the completion([]) in the error cases instead of return only.
I have a UITableviewCell in that cell I have added name, emp_id, and UIImageview to display the data I have 2 url's in which one url has displaying names and emp_id's and another Url have images along emp_id's(same emp_id's ) and I have to show that images to there names using help of emp_id's. I am able to show details but not able to implement the images here is my code
struct jsonstruct5:Decodable {
var name:String
var emp_id:String
var url:String?
}
struct jsonstruct21:Decodable {
var url:String?
var emp_id:String
}
var arrdata = [jsonstruct5]()
var arrdata1 = [jsonstruct21]()
func getdata(){
let url = URL(string: "https://sp/company/employees_detail/app")
URLSession.shared.dataTask(with: url!) { (data, response, error) in
do{if error == nil{
self.arrdata = try JSONDecoder().decode([jsonstruct5].self, from: data!)
for mainarr in self.arrdata{
// print(data)
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
}catch{
print("Error in get json data")
}
}.resume()
}
Response is
[
{
"name":"Sonu",
"emp_id":"01"
},
{
"name":"Prasanth",
"emp_id":"02"
},
{
"name":"Patra",
"emp_id":"03"
}.
]
func getdata1(){
let url = URL(string: "https://sp/company/employees_detail/profile/photos")
URLSession.shared.dataTask(with: url!) { (data, response, error) in
do{if error == nil{
self.arrdata1 = try JSONDecoder().decode([jsonstruct21].self, from: data!)
for mainarr1 in self.arrdata1{
// print(mainarr.name,":",mainarr.dob)
print(data)
print(mainarr1.url)
let data1 = try? Data(contentsOf: url!)
print(data1)
if let imageData = data {
let image4 = UIImage(data: imageData)
}
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
}catch{
print("Error in get json data")
print(error)
}
}.resume()
}
Response is
[
{
"url":"https//ps/Image2",
"emp_id":"01"
},
{
"url":null,
"emp_id":"02"
},
{
"url":"https//ps/Image4",
"emp_id":"03"
}
]
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:AppreTableViewCell = tableView.dequeueReusableCell(withIdentifier: "cell") as! AppreTableViewCell
cell.nameLbl.text = "\(arrdata[indexPath.row].name)"
cell.dateLbl.text = "\(arrdata[indexPath.row].emp_id)"
print(DataManager.sharedInstance.empID)
if (arrdata[indexPath.row].emp_id == DataManager.sharedInstance.empID)
{
cell.isHidden=true
}
else{
cell.isHidden=false
}
// tableView.alwaysBounceVertical = false
return cell
}
Try Below
struct jsonstruct5:Decodable {
var name:String
var emp_id:String
var url:String?
}
struct jsonstruct21:Decodable {
var url:String?
var emp_id:String
}
var arrdata = [jsonstruct5]()
var arrdata1 = [jsonstruct21]()
func getdata(){
let url = URL(string: "https://sp/company/employees_detail/app")
URLSession.shared.dataTask(with: url!) { (data, response, error) in
do { if error == nil {
self.arrdata = try JSONDecoder().decode([jsonstruct5].self, from: data!)
self. getdata1()
// don't for put reloadData in for loops, always play with your data/model and after loop you can call reloadData method.
}catch{
print("Error in get json data")
}
}.resume()
}
func getdata1(){
let url = URL(string: "https://sp/company/employees_detail/profile/photos")
URLSession.shared.dataTask(with: url!) { (data, response, error) in
do{if error == nil{
self.arrdata1 = try JSONDecoder().decode([jsonstruct21].self, from: data!)
// I don't know your model type, but you can put "url" from each iteration of second array (arrdata1) into first array (arrdata) where employee id matches.
// don't download images in below loop, rather download them in cellForRowAt method.
// then you just call self.tableView.reloadData()
// update your model
for item1 in arrdata1 {
if let index = arrdata.index(where: {$0["emp_id"] == item1["emp_id"]}) {
var item = arrdata[index]
item["url"] = item1["url"]
arrdata[index] = item
}
}
// or below code
for item1 in arrdata1 {
if let index = arrdata.index(where: {$0.emp_id == item1.emp_id}) {
var item = arrdata[index]
item.url = item1.url
arrdata[index] = item
}
}
// model array has been updated, now work on cellForRowAt method
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}catch{
print("Error in get json data")
print(error)
}
}.resume()
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:AppreTableViewCell = tableView.dequeueReusableCell(withIdentifier: "cell") as! AppreTableViewCell
let model = arrdata[indexPath.row]
cell.nameLbl.text = "\(model.name)"
cell.dateLbl.text = "\(model.emp_id)"
// here you download image for only current model.url
if let url = model.url {
cell.nameOfImageView.loadImageUsingCacheWithURLString(url, placeHolder: UIImage(named: "someImage")!, completionBlock: { (image) in
cell.nameOfImageView.image = image
})
}
// tableView.alwaysBounceVertical = false
return cell
}
let imageCache = NSCache<NSString, UIImage>()
extension UIImageView {
func loadImageUsingCacheWithURLString(_ URLString: String, placeHolder: UIImage, completionBlock:#escaping (_ image:UIImage)->()) {
if let cachedImage = imageCache.object(forKey: NSString(string: URLString)) {
self.image = cachedImage
completionBlock(cachedImage)
}
else 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!.localizedDescription)")
DispatchQueue.main.async {
self.image = placeHolder
completionBlock(placeHolder)
}
}
else {
DispatchQueue.main.async {
if let data = data {
if let downloadedImage = UIImage(data: data) {
imageCache.setObject(downloadedImage, forKey: NSString(string: URLString))
self.image = downloadedImage
completionBlock(downloadedImage)
}
else {
self.image = placeHolder
completionBlock(placeHolder)
}
}
else {
self.image = placeHolder
completionBlock(placeHolder)
}
}
}
}).resume()
}
else if URLString.isEmpty {
completionBlock(placeHolder)
}
}
}
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 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()
}}
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”