Im trying to upload a file to the backend. edit
I'm at a stand still with this code. I feel like I'm doing this wrong. Its returning (Status Code: 400, Headers ) Which makes me believe that the error is in the headers but I may be totally wrong because I see people using .upload() instead of .request. I've made 2 functions based on these two methods, neither one works. .request is error 400, upload() is error 500. I've tried the access token in L5 Swagger and it works. The only parameter is a file which I assume is data. Here are the methods
#discardableResult
public func postUploadSeekerAvatar(_ image: UIImage, result: #escaping (Error?) -> Void) -> URLSessionTask? {
let imageData: Data = UIImagePNGRepresentation(image)!
let params: [String: Any] = ["data": imageData]
var headers = authHeader
headers?["Content-Type"] = "application/json"
return Alamofire.request(endpointURL("users/seeker/avatar"), method: .post, parameters: params, headers: headers)
.responseData(completionHandler: { (response: DataResponse<Data>) in
guard response.result.error == nil else {
return result(response.result.error)
}
return result(response.result.error)
}).task
}
#discardableResult
public func postSeekerAvatar(_ image: UIImage, result: #escaping (Error?) -> Void) -> URLSessionTask? {
let data = UIImagePNGRepresentation(image)!
var headers = authHeader
headers?["Content-Type"] = "application/json"
return Alamofire.upload(data, to: endpointURL("users/seeker/avatar"), method: .post, headers: headers)
.responseData(completionHandler: { (response: DataResponse<Data>) in
guard response.result.error == nil else {
return result(response.result.error)
}
return result(response.result.error)
}).task
}
Can you sirs and madams please help a newbie? Thank you!
Related
I have an application where user can update their profile picture.
The application is developed in SwiftUI and used Alamofire to perform API request and the server is developed with Vapor.
When I tried to send the picture to the server, I got this error:
[ WARNING ] Value required for key 'filename'. [request-id: A5083FA1-657C-4777-A7FF-9D02E2A66703]
Here is the code from Vapor:
private func updatePicture(req: Request) async throws -> Response {
let file = try req.content.decode(File.self)
guard let fileExtension = file.extension else { throw Abort(.badRequest)}
return .init(status: .accepted, headers: getDefaultHttpHeader(), body: .empty)
}
And here is the iOS code:
func uploadFiles(urlParams: [String], method: HTTPMethod, user: User, image: UIImage, completionHandler: #escaping ((Data?, HTTPURLResponse?, Error?)) -> Void) {
guard let formattedUrl = URL(string: "\(url)/\(urlParams.joined(separator: "/"))") else {
completionHandler((nil, nil, nil))
return
}
var headers: HTTPHeaders?
headers = ["Authorization" : "Bearer \(user.token)"]
let multiPart: MultipartFormData = MultipartFormData()
multiPart.append(image.jpegData(compressionQuality: 0.9), withName: "data", fileName: "filename", mimeType: "image/jpeg" )
AF.upload(multipartFormData: multiPart, to: formattedUrl, method: .patch, headers: headers).response { data in
print(data)
}.resume()
}
I followed vapor and Alamofire documentation and I still get this issue.
Is anyone can help me with this issues ?
On the Vapor side you have to use struct with File field inside
struct PayloadModel: Content {
let data: File
}
private func updatePicture(req: Request) async throws -> HTTPStatus {
let payload = try req.content.decode(PayloadModel.self)
guard let fileExtension = payload.data.extension else {
throw Abort(.badRequest)
}
return .accepted
}
I was just wondering what would be the best way to call the same Web Service from Different View Controllers(at different time). What architecture or design should I follow? I don't want to write the same code in each View Controller.
In case of using Alamofire library I can suggest to use
class NetworkManager {
static let shared = NetworkManager()
static let alamofireManager: SessionManager = {
let sessionConfiguration = URLSessionConfiguration.default
sessionConfiguration.timeoutIntervalForRequest = TimeInterval(_TIMEOUT)
sessionConfiguration.timeoutIntervalForResource = TimeInterval(_TIMEOUT)
return Alamofire.SessionManager(configuration: sessionConfiguration)
}()
func performRequest(url: String,
method: HTTPMethod = .get,
parameters: [String: Any] = [String: Any](),
encoding: ParameterEncoding = URLEncoding.default,
contentType: String? = nil,
headers: HTTPHeaders = [String: String](),
success: #escaping(Data, Int) -> (),
failure: #escaping(CustomError) -> ()) {
debugPrint("NetworkManager is calling endpoint: \(url)")
NetworkManager.alamofireManager.request(url, method: method, parameters: parameters, encoding: encoding, headers: headers).validate().response { response in
guard let status = response.response?.statusCode, let data = response.data else {
if let error = response.error {
debugPrint("Error when calling endpoint \(url)")
failure(.unknownError(message: error.localizedDescription))
}
return
}
debugPrint("HTTP Status received: \(status)")
success(data, status)
}
} else {
failure(.noNetworkConnection)
}
}
Please feel free to modify failure handler with your custom error or whatever you like.
Of course then you need to serialise the response.
Create a base class for Alamofire request like that:
import Alamofire
/// Struct for create AlamofireRequestModal
struct AlamofireRequestModal {
/// Struct constant for Alamofire.HTTPMethod
var method: Alamofire.HTTPMethod
/// Struct constant for path
var path: String
/// Struct constant for parameters
var parameters: [String: AnyObject]?
/// Struct constant for encoding:ParameterEncoding
var encoding: ParameterEncoding
/// Struct constant for headers
var headers: [String: String]?
///method to get init
init() {
method = .post
path = ""
parameters = nil
encoding = JSONEncoding() as ParameterEncoding
}
}
///BaseService to call the api's
class BaseService: NSObject {
/// network variable for Reachability
let network = Reachability.init(hostname: "https://www.google.com")
/**
This is method for call WebService into Alamofire
- parameter alamoReq: this is AlamofireRequestModal type request
- parameter success: success response
- parameter failure: failer object
*/
func callWebServiceAlamofire(_ alamoReq: AlamofireRequestModal, success:#escaping ((_ responseObject: AnyObject?) -> Void), failure:#escaping ((_ error: NSError?) -> Void)) {
guard (network?.isReachable)! else {
debugPrint("\n No Network Connection")
return
}
let request = Alamofire.request(alamoReq.path, method: alamoReq.method, parameters: alamoReq.parameters, encoding: alamoReq.encoding, headers: alamoReq.headers)
// Call response handler method of alamofire
request.validate(statusCode: 200..<600).responseJSON(completionHandler: { response in
let statusCode = response.response?.statusCode
if let allHeaderField = response.response {
allHeaderField.setHeaders()
}
switch response.result {
case .success(let data):
if statusCode == 200 {
success(data as AnyObject)
} else {
failure(NSError.init(domain: "www.wen.com", code: 101010, userInfo: ["message": "Something went wrong. Please trt again."]))
}
case .failure(let error):
failure(error as NSError?)
}
})
}
}
Then create a service class according to use, like here I am creating Profile service class for login and registration and profile type all api's are added in this class so you can create multiple serice class according to use:
import Alamofire
///Profile service to call the profile api's
class ProfileService: BaseService {
/**
This is request to BaseService to get Login
- parameter email: User email id
- parameter password: User password to login
- parameter success: success response
- parameter failure: failure response
*/
func doLogin(email: String, password: String, success: #escaping ((_ response: AnyObject?) -> Void), failure: #escaping ((_ error: NSError?) -> Void)) {
var request = AlamofireRequestModal()
request.path = "www.yourpath.com"
request.parameters = ["email": email as AnyObject,
"password": password as AnyObject
]
callWebServiceAlamofire(request, success: success, failure: failure)
}
}
Now, You can call this doLogin method of Profile service from anywhere like Or you can create more layer like Model class and call this service from model class or You can call directly like this:
ProfileService().doLogin(email: "Email", password: "Password", success: { (response) in
// Code here for handle success response
}) { (error) in
// Code here for handle error
}
I would write a network manager class, that takes the web service parameters if any as arguments.
Here is a crude example of the architecture
class YourNetworkManager {
public func callSpecificWebService(argument : Type?, [optional closure to handle results]) {
// Generate the actual URL to be called here. Usually by
// appending a suffix to some constant base url
// The web service call mechanism goes here.
// This could either use the NSURLSession API
// or some third party library such as Alamofire
// Process the generic response conditions from the web service
// here. Pass on the specific parts to the calling method.
}
}
As I mentioned, this is a crude example. The more modularised you can make things, the better it will be.
Never pass your Views and/or ViewControllers to you NetworkManager class.
Suppose you have a NetworkManager class like this.
open class NetworkHelper {
class var sharedManager: NetworkHelper {
struct Static{
static let instance: NetworkHelper = NetworkHelper()
}
return Static.instance
}
func request(_ method: HTTPMethod, _ URLString: String, parameters: [String : AnyObject]? = [:], headers: [String : String]? = [:], completion:#escaping (Any?) -> Void, failure: #escaping (Error?) -> Void) {
let URL = "BASE_PATH" + "URLString"
Alamofire.request(URL, method: method, parameters: parameters, encoding: JSONEncoding.default, headers: headers)
.responseJSON { response in
switch response.result {
case .success:
completion(response.result.value!)
case .failure(let error):
failure(error)
guard error.localizedDescription == JSON_COULDNOT_SERIALISED else {
return
}
}
}
}
}
Now create a BaseViewController which inherit from UIViewController and write your API call with necessary parameters.
For example in an API call you only need userID as param all else is static.
class BaseViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
func makeThisApiCallWithUserID(userId: Int) {
NetworkHelper.sharedManager.request(.get, "api/v1/yourApiAddress", parameters: ["userId":userId as AnyObject],headers: ["Authorization":"Bearer agshd81tebsf8724j"], completion: { (response) in
}) { (error) in
}
}
}
No you should inherit those ViewController in which you want the same API call and you do not want to write the code again.
class FirstChildViewController: BaseViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.makeThisApiCallWithUserID(userId: 123)
}
}
class SecondChildViewController: BaseViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.makeThisApiCallWithUserID(userId: 13)
}
}
class ThirdChildViewController: BaseViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.makeThisApiCallWithUserID(userId: 3)
}
}
See I haven't write API code in FirstChildViewController, SecondChildViewController, ThirdChildViewController but still they can make the same API call with different parameters.
Do you use Alamofire?, if yes then I have good method for it, written in NetworkHelper Class.
import Foundation
import Alamofire
open class NetworkHelper {
class var sharedManager: NetworkHelper {
struct Static{
static let instance: NetworkHelper = NetworkHelper()
}
return Static.instance
}
func request(_ method: HTTPMethod
, _ URLString: String
, parameters: [String : AnyObject]? = [:]
, headers: [String : String]? = [:]
, onView: UIView?, vc: UIViewController, completion:#escaping (Any?) -> Void
, failure: #escaping (Error?) -> Void) {
let URL = BASE_PATH + URLString
Alamofire.request(URL, method: method, parameters: parameters, encoding: JSONEncoding.default, headers: headers)
.responseJSON { response in
switch response.result {
case .success:
completion(response.result.value!)
case .failure(let error):
failure(error)
guard error.localizedDescription == JSON_COULDNOT_SERIALISED else {
return
}
}
}
}
}
I want to fetch the google profile image when UID is given to me .
I have this reference but m getting how to hit this url .
Please anyone provide me some example.
EDIT:
Here is my code
var googleImageUrl: URL?{
let urlString = "https://www.googleapis.com/plus/v1/people/\(uid)?fields=image&key=AIzaSyBfjHpl8DjU0IGw9mXbvK6HoNpY"
return URL(string: urlString)
}
Alamofire.request(url)
.validate()
.responseJSON(completionHandler: { (response) in
if response.result.isSuccess {
let json = response.result.value as? [String: Any]
} else {
let apiError = ApiError(response: response)
}
})
when hitting this api, am always getting the error. Why i am not getting the response ?
You have two options:
Using Googles API, requires API key:
https://www.googleapis.com/plus/v1/people/{UID}?fields=image&key={YOUR_API_KEY}
Using Googles Picasaweb which does not require any API key:
http://picasaweb.google.com/data/entry/api/user/{UID}?alt=json
Code to get the image:
// Check what you want to return in the onCompletion and use it
func getImage(uid: String, onCompletion: #escaping (String) -> Void, onError: #escaping (NSError) -> Void) {
let url = "https://www.googleapis.com/plus/v1/people/\(uid)?fields=image&key=AIzaSyBfjHpl8DjU0IGw9mXbvK6HoNpY"
let headers = [
"Content-Type": "application/json"
]
Alamofire.request(url, method: .get, parameters: nil, encoding: JSONEncoding.default, headers: headers)
.responseJSON { response in
if let object = response.result.value as? [String:Any] {
print(object)
// use the onCompletion in here
}
}
}
The reference you listed points to the Google People API, which is different than the Google+ People API. If you want to use the Google People API you should use https://people.googleapis.com/v1/people/{UID}?personFields=photo&key={YOUR_API_KEY}
There are official examples at: https://developers.google.com/people/v1/read-people#get-the-person-for-a-google-account
I need to send a request to a server and get a response.
I have the following URL:
http://192.168.200.10:9044/api/tables/?filter={"open_visible":true,"related":false}
For the query I'm using Alamofire. Here are some ways how I do it:
1)
class func getTables(completion: #escaping (_ response : DataResponse<TablesResponse>) -> Void) {
SVProgressHUD.show()
let getTablesPath = apiEntryPoint + "tables/?filter={\"open_visible\":true,\"related\":false}"
Alamofire.request(getTablesPath)
.validate()
.responseObject { (response: DataResponse<TablesResponse>) in
SVProgressHUD.dismiss()
completion(response)
}
}
I get the error: screenshot
2)
class func getTables(completion: #escaping (_ response : DataResponse<TablesResponse>) -> Void) {
SVProgressHUD.show()
let getTablesPath = apiEntryPoint + "tables/?filter={%22open_visible%22:true,%22related%22:false}"
Alamofire.request(getTablesPath)
.validate()
.responseObject { (response: DataResponse<TablesResponse>) in
SVProgressHUD.dismiss()
completion(response)
}
}
I get the error.
3)
class func getTables(completion: #escaping (_ response : DataResponse<TablesResponse>) -> Void) {
SVProgressHUD.show()
var getTablesPath = apiEntryPoint + "tables/?filter="
let jsonParameters = ["open_visible":true, "related":false]
if let json = try? JSONSerialization.data(withJSONObject: jsonParameters, options: []) {
if let content = String(data: json, encoding: .utf8) {
getTablesPath += content
}
}
Alamofire.request(getTablesPath)
.validate()
.responseObject { (response: DataResponse<TablesResponse>) in
SVProgressHUD.dismiss()
completion(response)
}
}
I get the error.
4)
class func getTables(completion: #escaping (_ response : DataResponse<TablesResponse>) -> Void) {
SVProgressHUD.show()
let getTablesPath = apiEntryPoint + "tables/"
Alamofire.request(getTablesPath, parameters: ["open_visible":true, "related":false])
.validate()
.responseObject { (response: DataResponse<TablesResponse>) in
SVProgressHUD.dismiss()
completion(response)
}
}
I got all tables. Without taking into account the desired parameters. It's bad.
QUESTION
As I can send a request for the server, considering the necessary parameters.
Try
let urlParameters = yourParametersString.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLQueryAllowedCharacterSet())
because you can't just pass { and } characters in URL
You can use request parameter for more user-friendly.
Put your all request data into NSDictionary and send it to the server.
let parameters: NSDictionary = [
"YOUR_KEY": YOUR_VALUE,
]
// Both calls are equivalent
Alamofire.request(YOUR_SERVER_ULR, method: .post, parameters: parameters, encoding: JSONEncoding.default)
Alamofire.request(YOUR_SERVER_ULR, method: .post, parameters: parameters, encoding: JSONEncoding(options: []))
Another way is that, if you are passing some special character into your question parameter then you have encoded the URL.
let URL = YOUR_FINAL_URL_TO_SERVER.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLQueryAllowedCharacterSet())
You can check out Apple Document for more detail.
I have began to refactor my Alamofire api calls to keep them in a seperate file. The only problem is that I dont know how to return the statuscode as well.
Api file:
static func getCategories(_ catId: Int, response: #escaping (JSON) -> ()) {
let urlString = baseURL + ResourcePath.categories(catId: catId).description
Alamofire.request(urlString, encoding: JSONEncoding.default, headers: headers).responseJSON{ (responseData) -> Void in
let cCatData = JSON(responseData.result.value ?? [])
response(cCatData)
}
}
Then in my VC:
Api.getCategories(catId) { (JSON) -> () in
self.categories = JSON["catData"]
self.tableView.reloadData()
}
But I need to know if the status code is 200/400/404/422/500 and I dont want to use the .validate() function, I want to pass the status code
Normally if I would have everything in the same file I would get the status code by:
Alamofire.request("https://www.something", parameters: parameters, encoding: JSONEncoding.default, headers: headers)
.responseJSON() { response in
if let statusCode = response.response?.statusCode {
if statusCode == 200 {
}
}
If you want your closure to pass back the status code, then add an Int? parameter and pass it back:
static func getCategories(_ catId: Int, response: #escaping (JSON, Int?) -> ()) {
let urlString = baseURL + ResourcePath.categories(catId: catId).description
Alamofire.request(urlString, encoding: JSONEncoding.default, headers: headers).responseJSON { responseData in
let cCatData = JSON(responseData.result.value ?? [])
response(cCatData, responseData.response?.statusCode)
}
}
Or I might use more standard variable/parameter names:
static func getCategories(_ catId: Int, completionHandler: #escaping (JSON, Int?) -> ()) {
let urlString = baseURL + ResourcePath.categories(catId: catId).description
Alamofire.request(urlString, encoding: JSONEncoding.default, headers: headers).responseJSON { response in
let cCatData = JSON(response.result.value ?? [])
completionHandler(cCatData, response.response?.statusCode)
}
}
Either way, you can then do:
Api.getCategories(catId) { json, statusCode in
guard statusCode == 200 else {
print("status code not 200! \(statusCode)")
return
}
// if you got here, the status code must have been 200
}
So basically you want is something like this:
func returnStatusCode(_ urlPath: String, parameters: [String: AnyObject]? = nil, headers: [String: String]? = nil, encoding: ParameterEncoding = URLEncoding.default, completion: #escaping (_ statusCode: Int, _ responseData: [String: AnyObject]?) -> Void) {
// you can omit 'encoding:' if you'll set it to default since it will be default by default. \o/
Alamofire.request(urlPath, method: .get, parameters: parameters, encoding: encoding, headers: headers).responseJSON { (response) in
if response.result.isSuccess, let returnObject = response.result.value as? [String: AnyObject], let statusCode = response.response?.statusCode {
// do something...
completion(statusCode, returnObject)
}
}
}
use the callback to handle the status code alone