Hey I am trying to make alert message with error information from alamofire request, but it should display no matter which view is active, because this request is on separated class, How I can do that?
Request class:
class Json {
var loginToken = ""
public func login(userName: String, password: String, loginCompletion: #escaping (_ JSONResponse : Any?, _ error: Error?) -> ()) {
let loginrequest = JsonRequests.loginRequest(userName: userName, password: password)
makeWebServiceCall(urlAddress: URL, requestMethod: .post, params: loginrequest, completion: { (json, error) in
loginCompletion(json, error)
})
}
public func device(token: String, loginCompletion: #escaping (_ JSONResponse : Any?, _ error: Error?) -> ()) {
let deviceinfo = JsonRequests.getInformationFromConfig(token: token, config: "wireless", section: "#wifi-iface[0]", option: "mode")
makeWebServiceCall(urlAddress: URL, requestMethod: .post, params: deviceinfo, completion: { (json, error) in
loginCompletion(json, error)
})
}
let manager = Alamofire.SessionManager.default
private func makeWebServiceCall (urlAddress: String, requestMethod: HTTPMethod, params:[String:Any], completion: #escaping (_ JSONResponse : Any?, _ error: Error?) -> ()) {
manager.session.configuration.timeoutIntervalForRequest = 5
manager.request(urlAddress, method: requestMethod, parameters: params, encoding: JSONEncoding.default).responseJSON{ response in
print(response.timeline)
switch response.result {
case .success(let value):
let json = JSON(value)
if let message = json["error"]["message"].string, message == "Access denied" {
// LoginVC.performLogin(UserDefaults.standard.value(forKey: "saved_username"),UserDefaults.standard.value(forKey: "saved_password"))
print("Access denied")
}
if let jsonData = response.result.value {
completion(json, nil)
}
case .failure(let error):
completion(nil, error)
}
Call function:
public class LoginModel {
var loginToken = ""
internal func JsonResult (param1: String, param2: String){
Json().login(userName: param1, password: param2) { (json, error) in
print(json)
print(error)
if error != nil {
//Show alert
return
}
//Access JSON here
if let jsonResponse = json {
let jsonDic = JSON(jsonResponse)
print(jsonDic)
//Now access jsonDic to get value from the response
for item in jsonDic["result"].arrayValue {
self.loginToken = item["ubus_rpc_session"].stringValue}
print(self.loginToken)
if (jsonDic["result"].exists()){
print(jsonDic["result"]["ubus_rpc_session"].stringValue)
if (jsonDic["result"].arrayValue.contains("6")) {
self.loginToken = "6"
} else {
for item in jsonDic["result"].arrayValue {
self.loginToken = item["ubus_rpc_session"].stringValue
UserDefaults.standard.setValue(self.loginToken, forKey: "saved_token")
print(self.loginToken)
}
}
}
print("No result")
}
}
self.JsonDevice(param1: (UserDefaults.standard.value(forKey: "saved_token")! as! String))
}
If you want to pass the error and show the alert from calling controller then you can use add one more type with your completionHandler.
So instead of making completionHandler with type completion: #escaping (_ JSON : Any) make it like this completion: #escaping (_ JSONResponse : Any?, _ error: Error?).
Now when you get response with your api then Error is nil so call completion handler like this way.
completion(json, nil)
When you get failure response then pass nil as response with error.
completion(nil, error)
Now when you call this function check for the error.
retur.login(userName: userName.text!, password: password.text!) { (json, error) in {
if error != nil {
//Show alert
return
}
//Access JSON here
if let jsonDic = json as? JSON {
//Now access jsonDic to get value from the response
print(jsonDic["field_that_you_want_to_access"])
}
}
First create global func:
func topViewController(base: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? {
if let nav = base as? UINavigationController {
return topViewController(base: nav.visibleViewController)
}
if let tab = base as? UITabBarController {
let moreNavigationController = tab.moreNavigationController
if let top = moreNavigationController.topViewController , top.view.window != nil {
return topViewController(base: top)
} else if let selected = tab.selectedViewController {
return topViewController(base: selected)
}
}
if let presented = base?.presentedViewController {
return topViewController(base: presented)
}
return base
}
Than u can do something like this:
struct ErrorShower {
static func showErrorWith(title:String? = nil, message:String? = nil, complition:(() -> ())?){
if let _ = topViewController() as? UIAlertController {
return
}
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Ok", style: .default, handler: { _ in
complition?()
}))
topViewController()?.present(alert, animated: true, completion: nil)
}
}
And simple call from where u want:
ErrorShower.showErrorWith(message: err.message, complition: nil)
Related
I have created generic methods in NetworkManager but this serviceCall working only .get method APIs
if i use post method with parameters like below got error
error:
{
code = "-32700";
meaning = "Undefined array key \"params\"";
message = "Parse error";
}
func testServiceCall(){
let parameters = ["name": "testinggg", "job": "manager"] as [String : Any]
let request = RequestObject(params: parameters, method: .post, path: "https://reqres.in/api/users", isTokenNeed: false, vc: self)
WebService.sharedInstance.serviceCall(UserModel.self, with: request) { (response, error) in
print("user post response \(response)")
}
}
This is NetworkManager Class: here added parameters not working
class GeneralResponse<T: Codable>: Codable {
var jsonrpc: String
var result: T?
let error: ErrorClass?
}
struct ErrorClass: Codable {
let code, message, meaning: String?
}
struct RequestObject {
var params: [String: Any]?
var method: HTTPMethod
var path: String
var isTokenNeed: Bool = false
var vc: UIViewController?
}
class NetworkManager {
private let decoder: JSONDecoder
static let sharedInstance = NetworkManager()
public init(_ decoder: JSONDecoder = JSONDecoder()) {
self.decoder = decoder
}
public func serviceCall<T: Codable>(_ objectType: T.Type,
with request: RequestObject,
completion: #escaping (T?, Error?) -> Void) {
AF.request(request.path, method: request.method, parameters: request.params,encoding: JSONEncoding.default, headers: request.isTokenNeed ? ["Authorization" : "Bearer hnjjdshjdsh", "Accept": "application/json"] : ["" : ""])
.responseJSON { response in
switch response.result {
case .success(_):
do {
let data = response.data
let responseData = try self.decoder.decode(T.self, from: data ?? Data())
completion(responseData, nil)
} catch {
completion(nil, error)
print("in catch \(error.localizedDescription)")
}
case .failure(let AFError):
let error = AFError
print(error.localizedDescription)
print("failure error: \(error)")
}
}
}
}
I have this function:
class func cURL (urlT: String, Completion block: #escaping ((Profile) -> ())) {
GetJson.loadJsonFromUrl(fromURLString: urlT) { (result) in
switch result {
case .success(let data):
//Parse
if let decodedJson = GetJson.ParseJson(jsonData: data) {
block(decodedJson)
}
case .failure(let error):
print("loadJson error:", error)
}
}
}
And that's the function ParseJson, probably to modified too:
class func ParseJson(jsonData: Data) -> Profile? {
do {
let decodedData = try JSONDecoder().decode(Profile.self, from: jsonData)
return decodedData
} catch {
print("decode error: ",error)
}
return nil
}
How can I change the cURL function to return different types of struct, depending on the type of url it receives?
I call cURL this way :
cURL(urlT: encodedUrl) { (Json) in print(Json) }
For exemple here I give cURL a url1 and it returns a Json of type Profile.
What I try to do is, if I give a url2, I would like it to return a Json of type profile2.
I tried to use an enum with types but I can't get it to work.
Any help would be nice. Thanks.
It took me all night but I found a solution to this using generics:
class JSONParser {
typealias result<T> = (Result<T, Error>) -> Void
class func cURL2<T: Decodable>(of type: T.Type,
from url: String,
Completion block: #escaping ((Any) -> ()) ) {
download(of: T.self, from: url) { (result) in
switch result {
case .failure(let error):
print(error)
case .success(let response):
block(response)
}
}
}
class func download<T: Decodable>(of type: T.Type,
from urlString: String,
completion: #escaping result<T>) {
guard let url = URL(string: urlString) else {return}
URLSession.shared.dataTask(with: url) { (data, response, error) in
if let error = error {
print(error)
completion(.failure(error))
}
if let data = data {
do {
let decodedData: T = try JSONDecoder().decode(T.self, from: data)
completion(.success(decodedData))
}
catch {
print("decode error: ",error)
}
}
}.resume()
}
}
JSONParser.cURL2(of: Profile.self, from: url1) {(Json) in
print(Json)
}
I am trying to make a http request function for calling an API I want to use. I keep getting the error that NSDictionary needs to conform to Encodable but in the initializations before the call Ive already made it encodable.
What am I doing wrong?
Error message
Static method 'apiRequest(method:params:url:authToken:)' requires that 'NSDictionary' conform to 'Encodable'
Below is the initialization for the API request parameters.
private struct APIBody<CallParams: Encodable>: Encodable {
var method: String
var params: CallParams
var jsonrpc = "2.0"
var id = Int64(Date().timeIntervalSince1970)
}
private static let bodyEncoder: JSONEncoder = {
let e = JSONEncoder()
e.keyEncodingStrategy = .convertToSnakeCase
return e
}()
Below is the initialization for the API request to be sent.
private static func apiRequest<Params: Encodable>(
method: String,
params: Params,
url: URL,
authToken: String?
) throws -> URLRequest {
let body = APIBody(method: method, params: params)
var req = URLRequest(url: url)
req.httpMethod = "POST"
do {
req.httpBody = try bodyEncoder.encode(body)
} catch let e {
assertionFailure("API encoding error: \(e)")
throw e
}
req.addValue("application/json", forHTTPHeaderField: "Content-Type")
req.addValue("application/json", forHTTPHeaderField: "Accept")
if let authToken = authToken, !authToken.isBlank {
req.addValue(authToken, forHTTPHeaderField: "X-Lbry-Auth-Token")
}
return req
}
Below is the final call to the API where I get the error.
static func apiCall(
method: String,
params: [String: Any],
connectionString: String,
authToken: String? = nil,
completion: #escaping ([String: Any]?, Error?) -> Void
) {
let req: URLRequest
do {
req = try apiRequest( //<-- Error throws on this Line *********************
method: method,
params: params as NSDictionary,
url: URL(string: connectionString)!,
authToken: authToken
)
} catch let e {
completion(nil, e)
return
}
let task = URLSession.shared.dataTask(with: req, completionHandler: { data, response, error in
guard let data = data, error == nil else {
// handle error
completion(nil, error)
return
}
do {
Log.verboseJSON.logIfEnabled(.debug, "Response to `\(method)`: \(String(data: data, encoding: .utf8)!)")
let response = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any]
if response?["result"] != nil {
completion(response, nil)
} else {
if response?["error"] == nil, response?["result"] == nil {
completion(nil, nil)
} else if response?["error"] as? String != nil {
completion(nil, LbryApiResponseError(response?["error"] as! String))
} else if let errorJson = response?["error"] as? [String: Any] {
completion(nil, LbryApiResponseError(errorJson["message"] as! String))
} else {
completion(nil, LbryApiResponseError("unknown api error"))
}
}
} catch {
completion(nil, error)
}
})
task.resume()
}
I know theres multiple questions with the same topic, though the difference between my question and those seem to be defining parameters. Whats even odder is the code would build and install on my iPhone until yesterday.
This is where Xcode complains
private class func getLocItemsAtPath(path: String!, completionHandler: (LocItemsWrapper?, NSError?) -> Void) {
Alamofire.request(.GET, path!)
.responseLocItemsArray { response in
if let error = response.result.error
{
completionHandler(nil, error)
return
}
completionHandler(response.result.value, nil)
}
}
Here is the entire class
import Foundation
import Alamofire
import SwiftyJSON
enum LocItemFields: String {
case Name = "name"
case LocationBackground = "locationBackground"
case Logo = "logo"
case Status = "status"
case Company = "company"
case Id = "id"
case Url = "url"
}
class LocItemsWrapper {
var locItems: Array<LocItems>?
var count: Int?
private var next: String?
private var previous: String?
}
class LocItems {
var idNumber: Int?
var name: String?
var locationBackground: String?
var logo: String?
var status: String?
var company: String?
var id: String?
var url: String?
required init(json: JSON, id: Int?) {
//print(json)
self.idNumber = id
self.name = json[LocItemFields.Name.rawValue].stringValue
self.locationBackground = json[LocItemFields.LocationBackground.rawValue].stringValue
self.logo = json[LocItemFields.Logo.rawValue].stringValue
self.status = json[LocItemFields.Status.rawValue].stringValue
self.company = json[LocItemFields.Company.rawValue].stringValue
self.id = json[LocItemFields.Id.rawValue].stringValue
}
// MARK: Endpoints
class func endpointForLocItems(long: Double!, lat: Double!) -> String {
return Constants.getLocLoadListUrl() + "/" + String(long) + "/" + String(lat) + "/0"
}
private class func getLocItemsAtPath(path: String!, completionHandler: (LocItemsWrapper?, NSError?) -> Void) {
Alamofire.request(.GET, path!)
.responseLocItemsArray { response in
if let error = response.result.error
{
completionHandler(nil, error)
return
}
completionHandler(response.result.value, nil)
}
}
class func getLocItems(long: Double, lat: Double, completionHandler: (LocItemsWrapper?, NSError?) -> Void) {
getLocItemsAtPath(LocItems.endpointForLocItems(long,lat: lat), completionHandler: completionHandler)
}
class func getMoreLocItems(wrapper: LocItemsWrapper?, completionHandler: (LocItemsWrapper?, NSError?) -> Void) {
if wrapper == nil || wrapper?.next == nil
{
completionHandler(nil, nil)
return
}
getLocItemsAtPath(wrapper!.next!, completionHandler: completionHandler)
}
}
extension Alamofire.Request {
func responseLocItemsArray(completionHandler: Response<LocItemsWrapper, NSError> -> Void) -> Self {
let responseSerializer = ResponseSerializer<LocItemsWrapper, NSError> { request, response, data, error in
guard error == nil else {
return .Failure(error!)
}
guard let responseData = data else {
let failureReason = "Array could not be serialized because input data was nil."
let error = Error.errorWithCode(.DataSerializationFailed, failureReason: failureReason)
return .Failure(error)
}
let JSONResponseSerializer = Request.JSONResponseSerializer(options: .AllowFragments)
let result = JSONResponseSerializer.serializeResponse(request, response, responseData, error)
switch result {
case .Success(let value):
let json = SwiftyJSON.JSON(value)
let wrapper = LocItemsWrapper()
wrapper.next = json["next"].stringValue
wrapper.previous = json["previous"].stringValue
wrapper.count = json["count"].intValue
var allLocItems:Array = Array<LocItems>()
//print(json)
let results = json["rows"]
//print(results)
for jsonLocItems in results
{
//print(jsonLocItems.1)
let locItems = LocItems(json: jsonLocItems.1, id: Int(jsonLocItems.0))
allLocItems.append(locItems)
}
wrapper.locItems = allLocItems
return .Success(wrapper)
case .Failure(let error):
return .Failure(error)
}
}
return response(responseSerializer: responseSerializer,
completionHandler: completionHandler)
}
}
Any help would be greatly appreciated, Thank you.
EDIT: I got rid of the error by changing LocItemsWrapper? to LocItemsWrapper! but now I have an error saying ambiguous use of .responseLocItemsArray...
Xcode is trying to tell you that it can't figure out which .responseLocItemsArray to use, check all your other classes to see if you used it anywhere else.
As you can see, I'm receiving a JSON file, parsing it using SwiftyJSON, and trying to return totalTime, but it won't let me. How do I do this?
func googleDuration(origin: String, destination: String) -> Int{
// do calculations origin and destiantion with google distance matrix api
let originFix = origin.stringByReplacingOccurrencesOfString(" ", withString: "+", options: NSStringCompareOptions.LiteralSearch, range: nil);
let destinationFix = destination.stringByReplacingOccurrencesOfString(" ", withString: "+", options: NSStringCompareOptions.LiteralSearch, range: nil);
let urlAsString = "https://maps.googleapis.com/maps/api/distancematrix/json?origins="+originFix+"&destinations="+destinationFix;
println(urlAsString);
let url = NSURL(string: urlAsString)!
let urlSession = NSURLSession.sharedSession()
let task = urlSession.dataTaskWithURL(url, completionHandler: {data, response, error -> Void in
if error != nil {
// If there is an error in the web request, print it to the console
println(error.localizedDescription)
}
println("parsing JSON");
let json = JSON(data: data);
if (json["status"].stringValue == "OK") {
if let totalTime = json["rows"][0]["elements"][0]["duration"]["value"].integerValue {
println(totalTime);
}
}
})
task.resume();
}
You should add your own completionHandler closure parameter and call it when the task completes:
func googleDuration(origin: String, destination: String, completionHandler: (Int?, NSError?) -> Void ) -> NSURLSessionTask {
// do calculations origin and destiantion with google distance matrix api
let originFix = origin.stringByReplacingOccurrencesOfString(" ", withString: "+", options: NSStringCompareOptions.LiteralSearch, range: nil);
let destinationFix = destination.stringByReplacingOccurrencesOfString(" ", withString: "+", options: NSStringCompareOptions.LiteralSearch, range: nil);
let urlAsString = "https://maps.googleapis.com/maps/api/distancematrix/json?origins="+originFix+"&destinations="+destinationFix
println(urlAsString)
let url = NSURL(string: urlAsString)!
let urlSession = NSURLSession.sharedSession()
let task = urlSession.dataTaskWithURL(url) { data, response, error -> Void in
if error != nil {
// If there is an error in the web request, print it to the console
// println(error.localizedDescription)
completionHandler(nil, error)
return
}
//println("parsing JSON");
let json = JSON(data: data)
if (json["status"].stringValue == "OK") {
if let totalTime = json["rows"][0]["elements"][0]["duration"]["value"].integerValue {
// println(totalTime);
completionHandler(totalTime, nil)
return
}
let totalTimeError = NSError(domain: kAppDomain, code: kTotalTimeError, userInfo: nil) // populate this any way you prefer
completionHandler(nil, totalTimeError)
}
let jsonError = NSError(domain: kAppDomain, code: kJsonError, userInfo: nil) // again, populate this as you prefer
completionHandler(nil, jsonError)
}
task.resume()
return task
}
I'd also have this return the NSURLSessionTask in case the caller wants to be able to cancel the task.
Anyway, you'd call this like so:
googleDuration(origin, destination: destination) { totalTime, error in
if let totalTime = totalTime {
// use totalTime here
} else {
// handle error
}
}
Another example:
class func getExchangeRate(#baseCurrency: String, foreignCurrency:String, completion: ((result:Double?) -> Void)!){
let baseURL = kAPIEndPoint
let query = String(baseCurrency)+"_"+String(foreignCurrency)
var finalExchangeRate = 0.0
if let url = NSURL(string: baseURL + query) {
NSURLSession.sharedSession().dataTaskWithURL(url) { data, response, error in
if ((data) != nil) {
let jsonDictionary:NSDictionary = NSJSONSerialization.JSONObjectWithData(data!, options: nil, error: nil) as NSDictionary
if let results = jsonDictionary["results"] as? NSDictionary{
if let queryResults = results[query] as? NSDictionary{
if let exchangeRate = queryResults["val"] as? Double{
let priority = DISPATCH_QUEUE_PRIORITY_DEFAULT
dispatch_async(dispatch_get_global_queue(priority, 0)) {
dispatch_async(dispatch_get_main_queue()) {
completion(result: exchangeRate)
}
}
}
}
}
}
else {
completion(result: nil)
}
}.resume()
}
}
Call:
Currency.getExchangeRate(baseCurrency: "USD", foreignCurrency: "EUR") { (result) -> Void in
if let exchangeValue = result {
print(exchangeValue)
}
}
Another example:
func getJason(url: NSURL, completionHandler: (String?, NSError?) -> Void ) -> NSURLSessionTask {
var finalData: String!
let task = NSURLSession.sharedSession().dataTaskWithURL(url) { (data, response, error) -> Void in
if error != nil{
completionHandler(nil, error)
return
}
else{
if let urlContent = data{
do{
let jsonData = try NSJSONSerialization.JSONObjectWithData(urlContent, options: NSJSONReadingOptions.MutableContainers)
if let ip = jsonData["ip"]{
finalData = ip as? String
completionHandler(finalData, nil)
return
}
}catch{
print("EMPTY")
}
}
}
}
task.resume()
return task
}
Then i called it in the viewDidLoad
getJason(url) { (ipAddress, error) -> Void in
if error != nil{
print(error)
}
else{
if let ip = ipAddress{ //To get rid of optional
self.ipLabelDisplay.text = "Your Ip Address is: \(ip)"
}
}
}