I wanted to learn some NSURLSession basics and I was wondering how to handle multiple requests from an API. Like if you ask for a user resource on Github's api, it has an avatar_url, and then wanted to use that avatar_url to make another request. I have this so far:
let reposEndpoint = URL(string: "users/crystaltwix", relativeTo: baseURL)
var reposRequest = URLRequest(url: reposEndpoint!)
reposRequest.allHTTPHeaderFields = [
"accept": "application/vnd.github.v3+json",
"content-type": "application/json"
]
session?.dataTask(with: reposRequest) { data, response, error in
guard let response = response, let data = data else {
print("something went wrong")
return
}
print("response: \(response)")
// print("data: \(data)")
let decoder = JSONDecoder()
do {
let user = try decoder.decode(User.self, from: data)
print(user)
// avatar URL
let avatarURL = URL(string: user.avatarURL)
let avatarEndpoint = URLRequest(url: avatarURL!)
self.session?.dataTask(with: avatarEndpoint) { data, response, error in
guard let response = response, let data = data else {
print("something went wrong inner")
return
}
let avatarImage = UIImage(data: data)
let userModel = UserModel(login: user.login, avatar: avatarImage!, name: user.name, bio: user.bio)
}
} catch let error {
print("Error: \(error.localizedDescription)")
print(error)
completion(nil, nil, error)
}
}.resume()
struct User: Codable {
let login: String
let avatarURL: String
let name: String
let bio: String
private enum CodingKeys: String, CodingKey {
case login
case avatarURL = "avatar_url"
case name
case bio
}
}
So the first request to the user works and I make my URLRequest with the user.avatarURL fine, but then in the next
self.session?.dataTask(with: avatarEndpoint) { // nothing happens here }
No request is made there. What's the best way to handle this scenario?
I see your code, i think you forgot to call resume method in second request.
self.session?.dataTask(with: avatarEndpoint) { data, response, error in
guard let response = response, let data = data else {
print("something went wrong inner")
return
}
let avatarImage = UIImage(data: data)
let userModel = UserModel(login: user.login, avatar: avatarImage!, name: user.name, bio: user.bio)
}.resume()
resume method means start requesting.
Related
I cannot understand why this URLSession is not working on my playground. The URL works fine using curl commands on my terminal, so I know it is active, but I cannot see any list of names printed on my console.
The only print I see is the one "called" after that one, it seems there must be some error, but I have no clue about it, no message. Issue must be around the URLSession but cannot get where.
UPDATE
I added an extension to data found on stack, using right after this code
let (data, response) = try await URLSession.shared.data(from: url)
data.printJson()
The data is printed, but still cannot print anything in the for loop, where it should be.
extension Data {
func printJson() {
do {
let json = try JSONSerialization.jsonObject(with: self, options: [])
let data = try JSONSerialization.data(withJSONObject: json, options: .prettyPrinted)
guard let jsonString = String(data: data, encoding: .utf8) else {
print("Invalid data")
return
}
print(jsonString)
} catch {
print("Error: \(error.localizedDescription)")
}
}
}
And
import Foundation
//-----------------------------------------------
//MARK: - model
//-----------------------------------------------
struct ResponseRandom: Codable {
let users: [User]
}
struct User: Codable, Identifiable {
let id: Int
let first_name: String
let email: String
// "avatar": "https://robohash.org/rationeetsit.png?size=300x300&set=set1"
let date_of_birth: String//"1958-07-18"
}
//-----------------------------------------------
//MARK: - class
//-----------------------------------------------
class HTTPRequest_randomUsers {
// #Published var users = [UserRandom]()
init() {
Task {
await loadData()
}
}
func loadData() async {
print("called")
let numberOfItems = 50
guard let url = URL(string: "https://random-data-api.com/api/v2/users?size=2&response_type=json") else {
fatalError("URL error")
}
do {
let (data, response) = try await URLSession.shared.data(from: url)
guard let response = response as? HTTPURLResponse else {
print("not valid response")
return}
guard response.statusCode == 200 else {
print("not 200 status")
return}
let decoded = try JSONDecoder().decode([User].self, from: data)
print("decoded")
await MainActor.run {
// users = decoded.users
for item in decoded {
print(item.first_name)
}
}
} catch {
print("error: \(error)")
}
}
}
//here my call
let c = HTTPRequest_randomUsers()
Network requests run asynchronously (i.e., finish later). But by default, simple Playgrounds will stop when they reach the end of their path of execution. One must tell the Playground to continue execution indefinitely in order for the asynchronous results to be retrieved successfully:
import PlaygroundSupport
and
PlaygroundPage.current.needsIndefiniteExecution = true
I am trying to make a get request to a JWT enabled API but I am getting my result in bytes instead of objects
after the user logs in it is expected to go the next page where it lists user projects, at the moment I've created a button which when clicked, should send a Get Request with the JWT token received from the API function below.
I have tried:
func getUserProject(token: String, completion: #escaping (Result<[Project], NetworkError>) -> Void) {
guard let url = URL(string: "https://api.t.io/api/manage/user/projects?archived=false&logic=and&page=0&size=10") else {
completion(.failure(.invalidURL))
print("unable to connect URL🆘")
return
}
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.addValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
guard let data = data, error == nil else {
completion(.failure(.noData))
print("Bad🥺")
return
}
guard let Projects = try? JSONDecoder().decode([Project].self, from: data) else {
completion(.failure(.decodingError))
print(data)
print("Error Decoding Data🥵")
return
}
completion(.success(Projects))
}
task.resume()
}
This is the error I am getting from my logs:
this is my project model:
let createdBy: String
let createdDate: EdDate
let lastModifiedBy: String
let lastModifiedDate: EdDate
let id: Int
let name: String
let customID: JSONNull?
let shared: Bool
let salt: JSONNull?
let isCOMRequestAutomatic: Bool
let client: Client
let company: Company
let projectKeywords: [JSONAny]
let projectStartDate, projectEndDate: JSONNull?
enum CodingKeys: String, CodingKey {
case createdBy, createdDate, lastModifiedBy, lastModifiedDate, id, name, status
case imageURL
case customID
case shared, salt
case isCOMRequestAutomatic
case client, company, projectKeywords, projectStartDate, projectEndDate
}
} ```
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")
}
}
}
I want to verify phone number by getting one time password. But I am getting some error. Please look into the code bellow and help me to resolve it. I am using Twilio for mobile verification. And Alamofire for API request. But the error I am getting like:-
Authentication Error - No credentials provided.
The data couldn’t be read because it isn’t in the correct format
My code is:-
Here is my model class: -
...struct SendVerificationCode: Codable {
let status: String?
let payee: String?
let dateUpdated: Date?
let sendCodeAttempts: [SendCodeAttempt]?
let accountSid, to: String?
let amount: Int?
let valid: Bool?
let lookup: Lookup?
let url: String?
let sid: String?
let dateCreated: Date?
let serviceSid, channel: String?
enum CodingKeys: String, CodingKey {
case status, payee
case dateUpdated = "date_updated"
case sendCodeAttempts = "send_code_attempts"
case accountSid = "account_sid"
case to, amount, valid, lookup, url, sid
case dateCreated = "date_created"
case serviceSid = "service_sid"
case channel
}
}
struct Lookup: Codable {
let carrier: Carrier?
}
struct Carrier: Codable {
let mobileCountryCode, type: String?
let errorCode: String?
let mobileNetworkCode, name: String?
enum CodingKeys: String, CodingKey {
case mobileCountryCode = "mobile_country_code"
case type
case errorCode = "error_code"
case mobileNetworkCode = "mobile_network_code"
case name
}
}
struct SendCodeAttempt: Codable {
let channel, time: String?
}...
Api Request:-
...func sendcode(mobileWithCode: String, completion: #escaping sendTwillioVerificationCodeCompletion) {
let url = URL(string: SEND_TWILIO_VERIFICATION_CODE)
var urlRequest = URLRequest(url: url!)
urlRequest.httpMethod = HTTPMethod.post.rawValue
urlRequest.addValue(userNameData, forHTTPHeaderField: "Username")
urlRequest.addValue(PasswordData, forHTTPHeaderField: "Password")
urlRequest.addValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
Alamofire.request(urlRequest).responseJSON { (response) in
if let error = response.result.error {
debugPrint(error.localizedDescription)
completion(nil)
return
}
guard let data = response.data else { return completion(nil)}
Common.sharedInstance().printRequestOutput(data: data)
let jsonDecoder = JSONDecoder()
do {
let clear = try jsonDecoder.decode(SendVerificationCode.self, from: data)
completion(clear)
} catch {
debugPrint(error.localizedDescription)
completion(nil)
}
}
}...
But i am getting error:-
{"code": 20003, "detail": "Your AccountSid or AuthToken was incorrect.", "message": "Authentication Error - No credentials provided", "more_info": "https://www.twilio.com/docs/errors/20003", "status": 401}
"The data couldn’t be read because it isn’t in the correct format."
Also i have tried the following code:-
import Foundation
semaphore = DispatchSemaphore (value: 0)
let parameters = "To=+919778882332&Channel=sms"
let postData = parameters.data(using: .utf8)
var request = URLRequest(url: URL(string: myUrl)!,timeoutInterval: Double.infinity)
request.addValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
request.addValue(requestData, forHTTPHeaderField: "Authorization")
request.httpMethod = "POST"
request.httpBody = postData
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data else {
print(String(describing: error))
return
}
print(String(data: data, encoding: .utf8)!)
semaphore.signal()
}
task.resume()
semaphore.wait()
But i am getting error like
"Invalid parameter"
Twilio developer evangelist here.
It looks as though your code is trying to call the Twilio API directly from the device and that you weren't setting your Account SID or Auth Token in that.
The issue here is that you should not store or access your auth token from within your application. That would make your account sid and auth token vulnerable to be stolen and then used to abuse your account.
Instead, you should create a server side application that talks to the Twilio API and then call that from your application.
As Jamil pointed out, there is a blog post you can follow on performing phone verification in iOS with Twilio Verify and Swift and I recommend you go through that. It includes an example server side application to call the Twilio Verify API built in Python, but you could build your own too.
Here is sample code:
import UIKit
class ViewController: UIViewController {
static let path = Bundle.main.path(forResource: "Config", ofType: "plist")
static let config = NSDictionary(contentsOfFile: path!)
private static let baseURLString = config!["serverUrl"] as! String
#IBOutlet var countryCodeField: UITextField! = UITextField()
#IBOutlet var phoneNumberField: UITextField! = UITextField()
#IBAction func sendVerification() {
if let phoneNumber = phoneNumberField.text,
let countryCode = countryCodeField.text {
ViewController.sendVerificationCode(countryCode, phoneNumber)
}
}
static func sendVerificationCode(_ countryCode: String, _ phoneNumber: String) {
let parameters = [
"via": "sms",
"country_code": countryCode,
"phone_number": phoneNumber
]
let path = "start"
let method = "POST"
let urlPath = "\(baseURLString)/\(path)"
var components = URLComponents(string: urlPath)!
var queryItems = [URLQueryItem]()
for (key, value) in parameters {
let item = URLQueryItem(name: key, value: value)
queryItems.append(item)
}
components.queryItems = queryItems
let url = components.url!
var request = URLRequest(url: url)
request.httpMethod = method
let session: URLSession = {
let config = URLSessionConfiguration.default
return URLSession(configuration: config)
}()
let task = session.dataTask(with: request) {
(data, response, error) in
if let data = data {
do {
let jsonSerialized = try JSONSerialization.jsonObject(with: data, options: []) as? [String : Any]
print(jsonSerialized!)
} catch let error as NSError {
print(error.localizedDescription)
}
} else if let error = error {
print(error.localizedDescription)
}
}
task.resume()
}
}
For more please check this link: Link.
I wanted to be a type of variable to send to the dictionary server but But on the line I was faced with the problem let task = session.dataTaskWithRequest(todosUrlRequest) error : Cannot convert value of type 'NSURL' to expected argument type 'NSURLRequest'
I had two questions
1) What is this error?
2)Is there a procedure that I used for POST is that right? doesn't need anything else. ??
thank you for help
func data_request (){
let url = "http://sample.com/api/Flight/GetTicketInformation"
guard let todosUrlRequest = NSURL(string: url) else {
print("Error: cannot create URL")
return
}
let request = NSMutableURLRequest(URL: todosUrlRequest)
request.HTTPMethod = "POST"
request.cachePolicy = NSURLRequestCachePolicy.ReloadIgnoringCacheData
let newTodo = ["Roundtrip": roundTrip,
"OneWay": oneWay,
"MultiWay": multiWay,
"Adult": numberAdults,
"Child": numberchild,
"Baby": numberinfant,
"SourceCityId": cityIDOrigin,
"DestinationCityId": cityIDPurpose,
"DepartingDate": raftDate,
"ReturningDate": bargashtDate ]
let jsonTodo: NSData
do {
jsonTodo = try NSJSONSerialization.dataWithJSONObject(newTodo, options: [])
request.HTTPBody = jsonTodo
} catch {
print("Error: cannot create JSON from todo")
return
}
request.HTTPBody = jsonTodo
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: config)
let task = session.dataTaskWithRequest(todosUrlRequest) {
(data, response, error) in
guard let responseData = data else {
print("Error: did not receive data")
return
}
guard error == nil else {
print("error calling POST on /todos/1")
print(error)
return
}
// parse the result as JSON, since that's what the API provides
do {
guard let receivedTodo = try NSJSONSerialization.JSONObjectWithData(responseData,
options: []) as? [String: AnyObject] else {
print("Could not get JSON from responseData as dictionary")
return
}
print("The todo is: " + receivedTodo.description)
} catch {
print("error parsing response from POST on /todos")
return
}
}
task.resume()
}
request instead of todosUrlRequest on the line let task = session.dataTaskWithRequest(todosUrlRequest)
for the second question, no idea . sorry
I can recommend you Alamofire for all requests, instead of writing all code on your own.
https://github.com/Alamofire/Alamofire