This question already has answers here:
Returning data from async call in Swift function
(13 answers)
Closed 4 years ago.
Hi there i am currently working on a Api Manager in Swift, what i got so far:
import Foundation
import CoreData
import Alamofire
import SwiftyJSON
class ApiManager {
var data: NSArray = []
func getApi() -> NSArray {
let user:String = "user"
let password:String = "password"
Alamofire.request(.GET, "http://localhost/api/")
.authenticate(user: user, password: password)
.responseJSON{ (request, response, jsonData, error) in
if let jsonData1 = jsonData {
if let jsonData2 = JSON(jsonData1).dictionaryObject {
self.data = jsonData2["data"] as! NSArray
}
}
}
return data
}
}
The JSON Api is correct, but there is something wrong with my swift code, but i am not sure what it is,
When i call this manager:
let response = ApiManager().getApi()
println(response)
I just get empty brackets:
(
)
Anybody could help me with this?
This is a faulty design. You should NOT do it this way. Your getApi() method should have a success block which would be executed if the response was successful. Here is one possible solution:
static func getApi(success:( (data:NSArray) -> () )) {
let user:String = "user"
let password:String = "password"
Alamofire.request(.GET, "http://localhost/api/")
.authenticate(user: user, password: password)
.responseJSON{ (request, response, jsonData, error) in
if let jsonData1 = jsonData {
if let jsonData2 = JSON(jsonData1).dictionaryObject {
success(jsonData2 as! NSArray)
}
}
}
return data
}
So, if you want to print the response, you'll do something like:
APIManager.getApi {
println($0)
}
using URLSession you can write your APIManager class like this
import UIKit
import SwiftyJSON
class APIManager: NSObject {
let baseURL = "https://jsonplaceholder.typicode.com"
static let sharedInstance = APIManager()
static let getPostsEndpoint = "/posts/"
func getPostWithId(postId: Int, onSuccess: #escaping(JSON) -> Void, onFailure: #escaping(Error) -> Void){
let url : String = baseURL + APIManager.getPostsEndpoint
let request: NSMutableURLRequest = NSMutableURLRequest(url: NSURL(string: url)! as URL)
request.httpMethod = "GET"
print("Request ",request)
let session = URLSession.shared
let task = session.dataTask(with: request as URLRequest, completionHandler: {data, response, error -> Void in
if(error != nil){
onFailure(error!)
} else{
do {
let data = try JSON(data: data!)
print("\n Success data ",data)
onSuccess(data)
} catch let error as NSError {
print("Error ",error)
}
}
})
task.resume()
}
}
// and to use write definition for api call and response in your controller as follows
func getGETAPIData(){
APIManager.sharedInstance.getPostWithId(postId:1, onSuccess: { json in
DispatchQueue.main.async {
print("data in list view:",String(describing: json))
}
}, onFailure: { error in
print("error2 :")
})
}
// and call this method as
self.getGETAPIData()
Related
I'm trying to retrieve some data from an API, but I got an error: "The given data was not valid JSON ", Status code: 401
I think that is an authentication problem. How can I set the auth credentials to make the GET request?
This is the code for retrieving the data from the JSON.
func loadData()
{
guard let url = URL(string: getUrl) else { return }
URLSession.shared.dataTask(with: url) { data, res, err in
do {
if let data = data {
let result = try JSONDecoder().decode([ItemsModel].self, from: data)
DispatchQueue.main.async {
self.items = result
}
} else {
print(" No Data ")
}
} catch( let error)
{
print(res)
print(String(describing: error))
}
}.resume()
}
This is the code for the view :
struct GetView: View {
#ObservedObject var viewModel = ViewModel()
var body: some View {
NavigationView {
VStack {
List(viewModel.items, id: \.id) { item in
Text(item.year)
}
} .onAppear(perform: {
viewModel.loadData()
})
.navigationTitle("Data")
}
}
}
To handle authentication you must implement a delegate for your URLSession:
func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: #escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
completionHandler(.performDefaultHandling, challenge.proposedCredential)
}
However, your 401 error may be due to your code not sending a valid GET request to the server. You probably want to decode the 'res' value to determine the status code:
if let response = res as? HTTPURLResponse {
if response.statusCode != 200 {
// data may be JSON encoded but you should get some for
// statusCode == 401
}
}
Without knowing the kind of service you are connecting to it is hard to speculate if you need a GET or a POST. The URL you use may require a query parameter.
I found the solution. This is the code for Basic Auth :
func loadData() {
//Credentials
let username = ""
let password = ""
let loginString = "\(username):\(password)"
let loginData = loginString.data(using: String.Encoding.utf8)!
let base64LoginString = loginData.base64EncodedString()
//Request
guard let url = URL(string: getUrl) else {return}
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue("Basic \(base64LoginString)", forHTTPHeaderField: "Authorization")
//Setup Session
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
do {
if let data = data {
print(String(data: data, encoding: .utf8)!)
let result = try JSONDecoder().decode([ItemsModel].self, from: data)
DispatchQueue.main.async {
self.items = result
}
}
else {
print(" No Data ")
}
} catch( let error)
{
print(String(describing: error))
}
}
task.resume()
}
This question already has answers here:
Returning data from async call in Swift function
(13 answers)
Closed 1 year ago.
I'm trying to return json data from an api call. I'm able to access the json data successfully but am struggling to find a way / the best way to return it for access in my app. Thanks for any ideas!
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// make the api call and obtain data
let data = self.loadData()
print("inside viewDidLoad", data) // prints 'inside viewDidLoad emptyString'
}
func loadData() -> String {
var circData = "emptyString"
let session = URLSession.shared
let url = URL(string: "https://us-east-1.aws.webhooks.mongodb-realm.com/api/client/v2.0/app/cirdata-khyvx/service/cirData/incoming_webhook/cirlAPI")!
let task = session.dataTask(with: url, completionHandler: { data, response, error in
if let json = try? JSONSerialization.jsonObject(with: data!, options: []) {
// print("json: ", json) // prints the whole json file, verifying the connection works. Some 300kb of data.
// print("json file type: ", type(of: json)) // prints '__NSArrayI'
let jsonString = "\(json)"
circData = jsonString
// print("circData", circData) // prints the whole json file, verifying that the json string has been assigned to 'circData'
}
})
task.resume()
// print("after: ", circData) // prints 'after: emptyString'. It's as if the reassignment didn't take place.
return circData
}
}
You can't return a value synchronously becuase the api call that is fetching json data is asynchronous. You need to use a completion handler instead.
You can put breakpoints in different places inside the code to understand how the flow executes.
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.loadData(completion: { [weak self] (result, error) in
if let error = error {
print(error.localizedDescription)
}
if let result = result {
print(result)
}
})
}
func loadData(completion: #escaping (_ data: Any?, _ error: Error?) -> Void) {
let url = URL(string: "https://us-east-1.aws.webhooks.mongodb-realm.com/api/client/v2.0/app/cirdata-khyvx/service/cirData/incoming_webhook/cirlAPI")!
let task = URLSession.shared.dataTask(with: url, completionHandler: { data, response, error in
if let error = error {
completion(nil, error)
return
}
do {
if let data = data {
let json = try JSONSerialization.jsonObject(with: data, options: [.allowFragments])
completion(json, nil)
} else {
completion(nil, nil)
}
} catch {
completion(nil, error)
}
})
task.resume()
}
}
I have created one function for JSON parsing, which I am calling in every view controller, but i am unable to pass parameters from that function
i have created function in NSObject class:
func serviceCall(_ url: String, _ params:[String : Any], completion: #escaping (Data?, Error?) -> Void) {
let url = URL(string: url)!
var urlRequest = URLRequest(url: url)
urlRequest.httpMethod = "POST" //set http method as POST
do {
urlRequest.httpBody = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted) // pass dictionary to nsdata object and set it as request body
} catch let error {
print(error.localizedDescription)
}
URLSession.shared.dataTask(with: urlRequest) { data, response, error in
if let error = error {
completion(nil, error)
return
}
guard let data = data else {
preconditionFailure("No error was received but we also don't have data...")
}
completion(data, nil)
}.resume()
}
}
in registrationVC how to add parameters to this function
my parameters for registration service:
struct RegData: Codable {
var jsonrpc: String
var params: PostReg
}
struct PostReg: Codable{
var email: String
var password: String
var device_id: String
}
while calling serviceCall function how to add parameters to it
if i call like this in button action
let url = "https://e/api/reg"
let jsonpostParameters: [String: Any] = RegData(jsonrpc: "2.0", params: (PostLogin(email: nameTf.text!, password: passwordTf.text!, device_id: "2")))
self.fetch(url, jsonpostParameters) { (data: Data?, error: Error?) in
guard let dt = data else { return }
// convert data to JSON
print(dt)
error:
cannot convert a value [String:Any] to RegData
how to add RegData to serviceCall, shall i change serviceCall params type? if yes how..
how add RegData to serviceCall to parse JSON
Kindly try this for decode data
//Here ResponceData is your codable class
let dictData = try JSONSerialization.data(withJSONObject: data, options: .prettyPrinted)
let obj= try JSONDecoder().decode([ResponseData].self, from: dictData)
I'm try to do HTTP request to take list of repositories from GitHub in JSON format.
This is for my app that will contain list of repositories and function to search repo for name.
import Foundation
class NetworkService {
private init() {}
static let shared = NetworkService()
func getData(matching query: String, completionHandler: #escaping (Any) -> ()) {
let session = URLSession.shared
var searchUrlComponents = URLComponents()
searchUrlComponents.scheme = "https"
searchUrlComponents.host = "api.github.com"
searchUrlComponents.path = "search/repositories?"
searchUrlComponents.queryItems = [URLQueryItem(name: "q", value: query)]
let searchURL = searchUrlComponents.url!
print(searchUrlComponents.url!
)
session.dataTask(with: searchURL) { (data, response, error) in
guard let data = data else { return }
do {
let json = try JSONSerialization.jsonObject(with: data, options: [])
print(json)
} catch {
print(error)
}
}.resume()
}
}
In this part I've got error.
I'm try without URLComponent, and got the same error, code look like that:
import Foundation
class NetworkService {
private init() {}
static let shared = NetworkService()
func getData(matching query: String, completionHandler: #escaping (Any) -> ()) {
let session = URLSession.shared
let searchURL = URL(string: "https://api.github.com/search/repositories?q={swift}")!
print(searchURL)
session.dataTask(with: searchURL) { (data, response, error) in
guard let data = data else { return }
do {
let json = try JSONSerialization.jsonObject(with: data, options: [])
print(json)
} catch {
print(error)
}
}.resume()
}
}
The error like:
Fatal error: Unexpectedly found nil while unwrapping an Optional value
But when I make a request in a browser with such a URL ("https://api.github.com/search/repositories?q={swift}") then JSON is loaded.
The path of URLComponents must start with a slash and must not end with a question mark
searchUrlComponents.path = "/search/repositories"
And in the second example the URL is valid if you omit the braces
let searchURL = URL(string: "https://api.github.com/search/repositories?q=swift")!
The reason is that unlike the browser the API URL(string: does not implicitly encode the URL
i started working with the SwiftyJson Class in order to parse json from the web.
I started with this RestApiManager:
typealias ServiceResponse = (JSON, NSError?) -> Void
class RestApiManager: NSObject {
static let sharedInstance = RestApiManager()
let baseUrl = "http://api.randomuser.me/"
func getRandomUser(onCompletion: (JSON) -> Void) {
makeHttpGetRequest(baseUrl, onCompletion: { json, err -> Void in
onCompletion(json)
})
}
func makeHttpGetRequest(path: String, onCompletion: ServiceResponse) {
let request = NSMutableURLRequest(URL: NSURL(string: path)!)
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(request, completionHandler: { data, response, error in
let json: JSON = JSON(data)
onCompletion(json, error)
})
task.resume()
}
}
In my controller i called this method:
RestApiManager.sharedInstance.getRandomUser { json -> Void in
let results = json["results"]
for (index: String, subJson: JSON) in results {
println(subJson["user"]["gender"].stringValue)
}
}
But it just prints
(lldb)
and some useless information..
Anybody could help me with this problem?
I learned with this tutorial
https://www.youtube.com/watch?v=YX9vK11oX-E
and this git link:
https://github.com/SwiftyJSON/SwiftyJSON