Decode multiple images from URL in Swift 4 - swift

I have an API endpoint that outputs 2 image URLs. I have the code below that decodes and parses through one of the image URLs, but I require to get and parse through both of the images.
How do I modify the code below so that the final return is 2 images
import Foundation
import SwiftUI
enum ApodImageResponse {
case Success(image: UIImage, title: String)
case Failure
}
struct ApodApiResponse: Decodable {
var url: String
var title: String
}
class ApodImageProvider {
static func getImageFromApi(completion: ((ApodImageResponse) -> Void)?) {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
formatter.timeZone = TimeZone(abbreviation: "UTC")
let date = Date()
let urlString = "https://api.nasa.gov/planetary/apod?api_key=eaRYg7fgTemadUv1bQawGRqCWBgktMjolYwiRrHK&date=\(formatter.string(from: date))"
let url = URL(string: urlString)!
let urlRequest = URLRequest(url: url)
let task = URLSession.shared.dataTask(with: urlRequest) { data, urlResponse, error in
parseResponseAndGetImage(data: data, urlResponse: urlResponse, error: error, completion: completion)
}
task.resume()
}
static func parseResponseAndGetImage(data: Data?, urlResponse: URLResponse?, error: Error?, completion: ((ApodImageResponse) -> Void)?) {
guard error == nil, let content = data else {
print("error getting data from API")
let response = ApodImageResponse.Failure
completion?(response)
return
}
var apodApiResponse: ApodApiResponse
do {
apodApiResponse = try JSONDecoder().decode(ApodApiResponse.self, from: content)
} catch {
print("error parsing URL from data")
let response = ApodImageResponse.Failure
completion?(response)
return
}
let url = URL(string: apodApiResponse.url)!
let urlRequest = URLRequest(url: url)
let task = URLSession.shared.dataTask(with: urlRequest) { data, urlResponse, error in
parseImageFromResponse(data: data, urlResponse: urlResponse, error: error, apodApiResponse: apodApiResponse, completion: completion)
}
task.resume()
}
static func parseImageFromResponse(data: Data?, urlResponse: URLResponse?, error: Error?, apodApiResponse: ApodApiResponse, completion: ((ApodImageResponse) -> Void)?) {
guard error == nil, let content = data else {
print("error getting image data")
let response = ApodImageResponse.Failure
completion?(response)
return
}
let image = UIImage(data: content)!
let response = ApodImageResponse.Success(image: image, title: apodApiResponse.title)
completion?(response)
}
}

Related

Swift Rest API call example using Codable

I am following a tutorial on REST API calls with Swift and Codable. I cannot compile the following although I was careful when I typed all of it. Can anyone tell me what's wrong? Also, can anyone point me to a better tutorial? The error is:
Catch block is unreachable
and also
cannot find json in scope
import UIKit
import Foundation
struct Example: Codable {
let userId: Int
let id: Int
let title: String
let completed: Bool
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
func getJson(completion: #escaping (Example)-> ()) {
let urlString = "https://jsonplaceholder.typicode.com/todos/1"
if let url = URL(string: urlString) {
URLSession.shared.dataTask(with: url) {data, res, err in
if let data = data {
let decoder = JSONDecoder()
do {
let json: Example = try! decoder.decode(Example.self, from: data)
completion(json)
}catch let error {
print(error.localizedDescription)
}
}
}.resume()
}
}
getJson() { (json) in
print(json.id)
}
}
struct Example: Decodable {
let userId: Int
let id: Int
let title: String
let completed: Bool
}
struct APIRequest {
var resourceURL: URL
let urlString = "https://jsonplaceholder.typicode.com/todos/1"
init() {
resourceURL = URL(string: urlString)!
}
//create method to get decode the json
func requestAPIInfo(completion: #escaping(Result<Example, Error>) -> Void) {
let dataTask = URLSession.shared.dataTask(with: resourceURL) { (data, response, error) in
guard error == nil else {
print (error!.localizedDescription)
print ("stuck in data task")
return
}
let decoder = JSONDecoder()
do {
let jsonData = try decoder.decode(Example.self, from: data!)
completion(.success(jsonData))
}
catch {
print ("an error in catch")
print (error)
}
}
dataTask.resume()
}
}
class ViewController: UIViewController {
let apiRequest = APIRequest()
override func viewDidLoad() {
super.viewDidLoad()
apiRequest.requestAPIInfo { (apiResult) in
print (apiResult)
}
}
}
Instead of "do catch", you can use "guard let"
func getJson(completion: #escaping (Example)-> ()) {
let urlString = "https://jsonplaceholder.typicode.com/todos/1"
if let url = URL(string: urlString) {
URLSession.shared.dataTask(with: url) {data, res, err in
guard let data = data else {return print("error with data")}
let decoder = JSONDecoder()
guard let json: Example = try? decoder.decode(Example.self, from: data) else {return print("error with json")}
completion(json)
}.resume()
}
}
This does NOT handle errors, so my solution is just to an answer to your question, not a general solution for every similar cases.

How to make an API call with Swift?

So I'm practising trying to make API calls with Swift. The code is as below:
struct Example: Codable {
let userID: String
let ID: String
let title: String
let completed: String
}
func getJson(completion: #escaping (Example)-> ()) {
let urlString = "https://jsonplaceholder.typicode.com/todos/1"
if let url = URL(string: urlString) {
URLSession.shared.dataTask(with: url) {data, res, err in
if let data = data {
let decoder = JSONDecoder()
if let json: Example = try? decoder.decode(Example.self, from: data) {
completion(json)
}
}
}.resume()
}
}
getJson() { (json) in
print(json.ID)
}
However, I am unable to print out anything when getJson is called.
The example API I used is found here.
The tutorial that I used to help me write the code is found here.
Modified your variable name and data type exactly as your API response.
struct Example: Codable {
let userId: Int
let id: Int
let title: String
let completed: Bool
}
func getJson(completion: #escaping (Example)-> ()) {
let urlString = "https://jsonplaceholder.typicode.com/todos/1"
if let url = URL(string: urlString) {
URLSession.shared.dataTask(with: url) {data, res, err in
if let data = data {
let decoder = JSONDecoder()
do {
let json: Example = try! decoder.decode(Example.self, from: data)
completion(json)
}catch let error {
print(error.localizedDescription)
}
}
}.resume()
}
}
getJson() { (json) in
print(json.id)
}
You can also use CodingKey and can change your response during the init period.
struct Example: Codable {
var userID: Int
var ID: Int
var title: String
var completed: Bool
enum CodingKeys: String, CodingKey {
case userID = "userId"
case ID = "id"
case title = "title"
case completed = "completed"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
userID = try values.decode(Int.self, forKey: .userID)
ID = try values.decode(Int.self, forKey: .ID)
title = try values.decode(String.self, forKey: .title)
completed = try values.decode(Bool.self, forKey: .completed)
title = "Title: \(title)"
}
}
func getJson(completion: #escaping (Example)-> ()) {
let urlString = "https://jsonplaceholder.typicode.com/todos/1"
if let url = URL(string: urlString) {
URLSession.shared.dataTask(with: url) {data, res, err in
if let data = data {
do {
let json: Example = try JSONDecoder().decode(Example.self, from: data)
completion(json)
}catch let error {
print(error.localizedDescription)
}
}
}.resume()
}
}
getJson() { (json) in
print("ID: \(json.ID) \(json.title)")
}
import UIKit
class ApiManager: NSObject {
static func postApiRequest(url: String,parameters:[String: Any],completion: #escaping([String: Any])-> Void) {
let session = URLSession.shared
let request = NSMutableURLRequest(url: NSURL(string: url)! as URL)
request.httpMethod = "POST"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
do{
request.httpBody = try JSONSerialization.data(withJSONObject: parameters, options: JSONSerialization.WritingOptions())
let task = session.dataTask(with: request as URLRequest as URLRequest, completionHandler: {(data, response, error) in
if let response = response {
let nsHTTPResponse = response as! HTTPURLResponse
let statusCode = nsHTTPResponse.statusCode
print ("status code = \(statusCode)")
}
if let error = error {
print ("\(error)")
}
if let data = data {
do{
let jsonResponse = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions())
print ("data = \(jsonResponse)")
}catch _ {
print ("OOps not good JSON formatted response")
}
}
})
task.resume()
}catch _ {
print ("Oops something happened buddy")
}
}
}

Thread 1: EXC_BREAKPOINT (code=1, subcode=0x10136bb50) - swift

I got this error when I want to send value with GET method:
Thread 1: EXC_BREAKPOINT (code=1, subcode=0x10136bb50)
To get values:
var flname = self.txt_field_flname.text
var job_title = self.txt_field_job_title.text
var mobile = self.txt_field_mobile.text
var des = self.txt_field_des.text
var lat = self.lat
var lon = self.lon
self.sendNewJob(fname: flname!, title: job_title!, mobile: mobile!, des: des!, lat: String(lat), lon: String(lon) )
func sendNewJob(fname:String,title:String,mobile:String,des:String,
lat:String,lon:String)
{
print("fname \(fname) title \(title) mobile \(mobile) des \(des) lat \(lat) lon \(lon)") //output is well
RestApiManager.sharedInstance.sendNewJob(fname: fname,title: title,mobile:mobile,
des:des,lat:lat,lon:lon) { (json: JSON) in
}
}
func sendNewJob(fname:String,title:String,mobile:String,des:String,
lat:String,lon:String,onCompletion: #escaping (JSON) -> Void) {
let route = baseURL+"up=1&Name=\(fname)&BusinessName=\(title)&MobileNumber=\(mobile)&latitude=\(lat)&longitude=\(lon)&Description=\(des)"
makeHTTPGetRequest(path: route, onCompletion: { json, err in
onCompletion(json as JSON)
})
}
// MARK: Perform a GET Request
private func makeHTTPGetRequest(path: String, onCompletion: #escaping ServiceResponse) {
let request = NSMutableURLRequest(url: NSURL(string: path)! as URL) // line of my error
let session = URLSession.shared
let task = session.dataTask(with: request as URLRequest, completionHandler: {data, response, error -> Void in
if let jsonData = data {
let json:JSON = JSON(data: jsonData)
onCompletion(json, error as NSError?)
} else {
onCompletion(nil, error as NSError?)
}
})
task.resume()
}
This happens when the code executes a nil value. Here the code NSURL(string: path)! value might be nil. You can use optional binding (if let) to check whether the NSURL is a valid one. It happens when the string is not valid and does not make a valid URL.
You can use like this :
private func makeHTTPGetRequest(path: String, onCompletion: #escaping ServiceResponse) {
if let encodedPath = path.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) {
if let url = URL(string: encodedPath) {
let request = URLRequest(url: url)
let session = URLSession.shared
let task = session.dataTask(with: request as URLRequest, completionHandler: {data, response, error -> Void in
if let jsonData = data {
let json:JSON = JSON(data: jsonData)
onCompletion(json, error as NSError?)
} else {
onCompletion(nil, error as NSError?)
}
})
task.resume()
} else {
print("url is nil")
onCompletion(nil)
}
} else {
print("unable to encode url")
onCompletion(nil)
}
}

Callback syntax in swift 3

I am trying to create a callback on swift 3 but haven't had any luck so far. I was taking a look at this question: link which is similar, but the answer gives me an error.
Basically I have an API struct with a static function that I need to have a callback.
import UIKit
struct API {
public static func functionWithCallback(params: Dictionary<String, String>, success: #escaping ((_ response: String) -> Ticket), failure: #escaping((_ error:String) -> String) ) {
let app_server_url = "http://api.com" + params["key"]!
let url: URL = URL(string: app_server_url)!
var request: URLRequest = URLRequest(url: url)
request.httpMethod = "POST"
do {
request.httpBody = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
} catch let error {
print(error.localizedDescription)
}
request.addValue("application/json charset=utf-8", forHTTPHeaderField: "Content-Type")
request.addValue("application/json charset=utf-8", forHTTPHeaderField: "Accept")
let session = URLSession.shared
let task = session.dataTask(with: request as URLRequest, completionHandler: { data, response, error in
guard error == nil else {
return
}
guard let data = data else {
return
}
DispatchQueue.main.async {
do {
let json = try JSONSerialization.jsonObject(with: data) as! [String: Any]
print(json)
var message = ""
if let result = json["result"] as? String {
if(result == "success") {
//attempt to call callback gives me an error: extra argument in call
success("") {
let ticket = json["ticket"] as! NSDictionary
var date = ticket["date"] as! String
var ticket: Ticket = nil
ticket.setDate(date: date)
return ticket
}
}
else {
message = json["message"] as! String
print(message)
}
} catch let error {
print(error.localizedDescription)
let description = error.localizedDescription
if let data = description.data(using: .utf8) {
do {
let jsonError = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any]
let message = jsonError?["message"] as! String
} catch {
}
}
}
}
})
task.resume()
}
}
So I basically can't call the callback success because it gives me an error: Extra argument in call. Any idea on how to fix it?
My goal is to call:
API.functionWithCallback(params: params, success() -> Ticket {
//do something with the returned ticket here
},
error() -> () {
//do something with the error message here
}
)
I believe you have it wrong on how to use call back closures, from what I can understand of your question you want to do something with the ticket in the call back closure and to do that it should be a parameter of the closure not the return type of the closure.
Replace your function declaration with this:
public static func functionWithCallback(params: Dictionary<String, String>, success: #escaping ((_ response: String, _ ticket: Ticket) -> Void), failure: #escaping((_ error:String) -> Void) ) {
And inside the function replace this:
success("") {
let ticket = json["ticket"] as! NSDictionary
var date = ticket["date"] as! String
var ticket: Ticket = nil // Im not sure what you are trying to do with this line but this will definitely give an error
ticket.setDate(date: date)
return ticket
}
With:
let ticket = json["ticket"] as! NSDictionary
var date = ticket["date"] as! String
var ticket: Ticket = nil // fix this line
ticket.setDate(date: date)
success("",ticket)
And then you can call the function like this:
API.functionWithCallback(params: params, success: { response, ticket in
// you can use ticket here
// and also the response text
}) { errorMessage in
// use the error message here
}
Try this :
func uploadImage(api: String,token : String, methodType : String, requestDictionary: [String:AnyObject],picData:[Data], successHandler: #escaping (AnyObject) -> Void,failureHandler: #escaping (NSError) -> Void)
{
if Common_Methods.Reachability1.isConnectedToNetwork() == false
{
let del :AppDelegate = (UIApplication.shared.delegate as? AppDelegate)!
let nav : UINavigationController = (del.window?.rootViewController as? UINavigationController)!
let alert = UIAlertController(title: "", message: "The Internet connection appears to be offline" , preferredStyle: UIAlertControllerStyle.alert)
// Create the actions
let okAction = UIAlertAction(title: "OK", style: UIAlertActionStyle.default)
{
UIAlertAction in
}
alert.addAction(okAction)
nav.present( alert, animated: true, completion: nil)
return
}
let apiUrl = "\(KbaseUrl)\(api)"
let session = URLSession.shared
let url: NSURL = NSURL(string: apiUrl as String)!
print(url)
let request = NSMutableURLRequest(url: url as URL)
request.httpMethod = methodType
let boundary = NSString(format: "---------------------------14737809831466499882746641449") as String
//-------- add token as perameter and set a check if token not nill then set token in header -------
if(token.characters.count > 0)
{
request.setValue(token, forHTTPHeaderField: "x-logintoken")
}
request.setValue("Keep-Alive", forHTTPHeaderField: "Connection")
request.setValue("multipart/form-data; boundary="+boundary, forHTTPHeaderField: "Content-Type")
let data = createBodyWithParameters(parameters: requestDictionary, filePathKey:nil, imageDataKey: picData.count > 0 ? picData : [], boundary: boundary)
print(data)
request.httpBody = data
let task = session.dataTask(with: request as URLRequest) { data, response, error in
// handle fundamental network errors (e.g. no connectivity)
guard error == nil && data != nil else {
successHandler(data as AnyObject )//completion(data as AnyObject?, error as NSError?)
print(error)
DispatchQueue.main.async {
Common_Methods.hideHUD(view: (topVC?.view)!)
}
return
}
// check that http status code was 200
if let httpResponse = response as? HTTPURLResponse , httpResponse.statusCode != 200 {
do {
let responseObject = try JSONSerialization.jsonObject(with: data!, options: []) as? NSDictionary
if let responseDictionary = responseObject as? [String:AnyObject]
{
if responseDictionary["statusCode"] as! Int == 401
{
// self.objDelegate.sessionExpire(msgStr: "Session Expired. Please login again to continue.")
}
else
{
//completion(String(data: data!, encoding: String.Encoding.utf8) as AnyObject?, nil)
}
}
} catch let error as NSError {
print(error)
DispatchQueue.main.async {
Common_Methods.hideHUD(view: (topVC?.view)!)
}
// completion(String(data: data!, encoding: String.Encoding.utf8) as AnyObject?, nil)
}
}
// parse the JSON response
do {
DispatchQueue.main.async {
Common_Methods.hideHUD(view: (topVC?.view)!)
}
let responseObject = try JSONSerialization.jsonObject(with: data!, options: []) as? NSDictionary
successHandler(responseObject! )
} catch let error as NSError {
DispatchQueue.main.async {
Common_Methods.hideHUD(view: (topVC?.view)!)
}
// completion(String(data: data!, encoding: String.Encoding.utf8) as AnyObject?, error)
failureHandler(error)
}
}
task.resume()
// return task
}
and function Call is :
WebService.sharedInstance.uploadImage(api: KEditEmployerProfile,token: token,methodType: "PUT", requestDictionary: parameters1 as! [String : AnyObject], picData: [imageData as Data], successHandler: { (responseObject) in
print(responseObject)
}) { (error) in
print(error)
}
}

How to wait for completion dataTaskWithRequest - Swift?

I try to compare files (tiles) replace the old file on the phone new file from the site, if necessary. But returnPathFile in func pathDir is assigned reference to a file without waiting for the check for and download a new file. How do I get to wait until the completion of testing and loading (if necessary) of the new file?
let urls = { (x: UInt, y: UInt, zoom: UInt) -> NSURL in
let pathtodir = "tiles/\(zoom)/\(x)"
let pathtofile = "\(y).png"
let url = FileManagement.pathDir(pathtodir, file: pathtofile)
return url
}
layerTile = GMSURLTileLayer(URLConstructor: urls)
layerTile.map = self.viewMap!
Method of analyzing whether there is a file in iPhone:
class func pathDir(dir: String, file: String) -> NSURL {
var returnPathFile = NSURL(string: "http://****.***/\(dir)/\(file)")
let url = NSURL(string: "http://*****.***/\(dir)/\(file)")
let pathFile = applicTempDir().stringByAppendingPathComponent("\(dir)/\(file)")
let documentsUrl = NSURL(fileURLWithPath: NSTemporaryDirectory()).URLByAppendingPathComponent("\(dir)/\(file)")
var attributesToInt: Int!
if filemgr.fileExistsAtPath("\(pathFile)") {
returnPathFile = NSURL(fileURLWithPath: pathFile)
let fileManager = NSFileManager.defaultManager()
do {
let attributes = try fileManager.attributesOfItemAtPath(pathFile)["NSFileCreationDate"] as! NSDate
attributesToInt = Int(attributes.timeIntervalSinceReferenceDate)
}
catch let error as NSError {
print("Ooops! Something went wrong: \(error)")
}
JsonData.isConnectedToNetwork(url!, dateFileOnTemp: attributesToInt, urlFileOnTemp: documentsUrl, pathFileOnTemp: pathFile, completion:{(path:String, error:NSError!) in
})
if filemgr.fileExistsAtPath("\(pathFile)") {
returnPathFile = NSURL(fileURLWithPath: pathFile)
return returnPathFile!
} else {
return returnPathFile!
}
}
}
Method compere two files:
class func isConnectedToNetwork(url: NSURL, dateFileOnTemp: Int, urlFileOnTemp: NSURL, pathFileOnTemp: String, completion:(path:String, error:NSError!) -> Void) {
var dates: String!
let sessionConfig = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: sessionConfig, delegate: nil, delegateQueue: nil)
let request = NSMutableURLRequest(URL: url)
request.HTTPMethod = "HEAD"
let task = session.dataTaskWithRequest(request, completionHandler: { (data: NSData?, response: NSURLResponse?, error: NSError?) -> Void in
if (error == nil) {
if let response = response as? NSHTTPURLResponse {
dates = response.allHeaderFields["Last-Modified"] as! String
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "EEE',' dd MMM yyyy HH':'mm':'ss 'GMT'"
dateFormatter.timeZone = NSTimeZone(abbreviation: "GMT")
let dateFileOnSite = dateFormatter.dateFromString(dates)
let dateFileOnSiteInt = Int(dateFileOnSite!.timeIntervalSinceReferenceDate)
if dateFileOnSiteInt > dateFileOnTemp {
FileManagement.reDelFile(pathFileOnTemp)
JsonData.reLoadFileAsync(url, destinationUrl: urlFileOnTemp as NSURL, completion:{(path:String, error:NSError!) in
})
}
}
}
else {
completion(path: urlFileOnTemp.path!, error:error)
}
})
task.resume()
}
Method loads new file:
class func reLoadFileAsync(url: NSURL, destinationUrl: NSURL, completion:(path:String, error:NSError!) -> Void) {
let sessionConfig = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: sessionConfig, delegate: nil, delegateQueue: nil)
let request = NSMutableURLRequest(URL: url)
request.HTTPMethod = "GET"
let task = session.dataTaskWithRequest(request, completionHandler: { (data: NSData?, response: NSURLResponse?, error: NSError?) -> Void in
if (error == nil) {
if let response = response as? NSHTTPURLResponse {
if response.statusCode == 200 {
if data!.writeToURL(destinationUrl, atomically: true) {
completion(path: destinationUrl.path!, error:error)
} else {
let error = NSError(domain:"Error saving file", code:1001, userInfo:nil)
completion(path: destinationUrl.path!, error:error)
}
}
}
}
else {
completion(path: destinationUrl.path!, error:error)
}
})
task.resume()
}
Try to use NSOperationQueue and add your operations accordingly to the queue. This thread might help you NSOperation and NSOperationQueue working thread vs main thread