I am a beginner in iOS development, recently I am learning about networking with rest API in iOS development. When constructing URL to make request, I found code like this:
var queryComponents: [URLQueryItem] {
var components = [URLQueryItem]()
for (key, value) in parameters {
let queryItem = URLQueryItem(name: key, value: "\(value)")
components.append(queryItem)
}
return components
}
I don't understand why the array component shall be in URLQueryItem data type.
Thanks in advance :)
Actually in URLComponents has a property called queryItems which requires array of URLQueryItem, Its purpose is to add number of parameters in the API request
In your case why they would have implemented queryComponents as an array is that they would have been assigning multiple URLQueryItems for a request in queryComponents.
Eg:
queryComponents.append(NEW_URLQueryItem1)
queryComponents.append(NEW_URLQueryItem2)
And finally when we access queryComponents we will get array value which we can directly assign to URLComponents as follows,
var urlComponents = URLComponents(string: "API_URL")
urlComponents.queryItems = queryComponents
Related
This question already has answers here:
Swift - encode URL
(19 answers)
Closed 2 years ago.
I have a URL that takes any String like this:
https://test#api.search?query=\(productSearchString)&limit=1
Now my problem is that this works with for example "iphone" but it crashes with "iphone 12".
I tried encoding it like this:
guard let escapedResourceString = resourceString.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) else { return }
but this doesn't seem to work in my case because it is returning an incorrect URL that I can not use with my API.
How can I fix this, so the user can type in anything he want but the URL should be created and my API should be able to work with it.
If you need anything more just let me know.
Your parameters needs to be url encoded.
You can use URLComponents to create your URL and pass your parameters as URLQueryItem instances, like that:
let productSearchString = "iphone 12"
var urlComponents = URLComponents(string: "https://test#api.search")
urlComponents?.queryItems = [
URLQueryItem(name: "query", value: productSearchString),
URLQueryItem(name: "limit", value: "1")
]
let url = urlComponents?.url
print(url?.absoluteString ?? "nil") // https://test#api.search?query=iphone%2012&limit=1
When I Use the Alamofire Function With Some CoreData Like UserDefaulfts.standard.object then the xcode will Throws an Error Extra Argument in call i tried approx all the Ans from Stackoverflow Like HTTPMethod and .JSONEncoding but not Happening
func Sync(){
let url = "http://abcd.com/reportlist"
let parameters = ["sort_by":"month_year",
"start":"1",
"end":"10"]
let key2 = UserDefaults.standard.object(forKey: "accessToken")
let headers = [
"Authorization": key2,
"Content-Type": "application/x-www-form-urlencoded"]
Alamofire.request(url, method: .post, parameters: parameters, headers: headers).responseJSON { response in
if response.result.isSuccess
{
var dictVal = response.result.value
let data1 = response.data
.................
Hold option key and press object(forKey function to read documentation hint. You will see that return type of that function is optional since nothing can be stored under the key you pass, right? So your key2 constant also is of Optional type.
Then when you use it in dictionary, headers itself becomes of type "String: Optional(String)" as one of values is Optional (key2).
Alamofire expects [String: String] dictionary type in headers parameter and since the type of your headers dict does not match it generates this error - extra argument. Like I don't know why you pass it to me I do not expect anything of type like that
That is it
So you either use string(forKey method which will return empty string in case nothing is stored under the key passed, or provide default value like that
let key2 = (UserDefaults.standard.object(forKey: "accessToken") as? String) ?? "defaultAccessTokenOrNothing" which has the same result as first suggestion
the Key used here is a String Type and in Header passing, u did not Mention the type of variable in a header
before I used this
let key2:String = UserDefaults.standard.object(forKey: "accessToken") as! String
xcode is again Happy to Code...
Hello I am trying to add queryItems to my URLComponents but because URLQueryItem returns its value as an optional the url keeps having a question mark in it.
var params = ["id":"5"]
let queryParams = toStringDictionary(dictionary: params).map { pair in
return URLQueryItem(name: pair.key, value: pair.value)
}
var components = URLComponents(string: fullURL)
components.queryItems = queryParams
print(components.url)//This is the full url https://demo.com/users/?5
//This https://demo.com/users/?5 should be this https://demo.com/users/5
The question mark is of course resulting in a wrong url. I can't get rid of it.
Query parameters are separated from the rest of the url by a question mark, for example:
https://example.com/path?param_name=param_value
The question mark is not due to an optional, but is the way that query params are supplied.
The example in your comment looks like it has lost the name of the param, I would expect it to read
https://demo.com/users/?id=5
If it definitely doesn't include the id= bit, please could you share the implementation of toStringDictionary so that we can see what's going on there? The dictionary you pass in is already a string dictionary, so seems odd to have such a function.
EDIT: following your clarifications (which have now disappeared, but made it clear that you were looking to add to the path rather than the query string)
It looks like your code is adding query parameters correctly; look closer at the URL it produces, it will be https://demo.com/users/?id=5. Query parameters are not part of the path, they come at the end and are separated from the rest of the URL by a ?
From your edits, it looks as though what you actually want to do is to add to the path of the URL, so that you get https://demo.com/users/5. That URL does not have any query parameters, so URLQueryItem is the wrong tool for the job.
To add to the path, you can do something like the following:
let userID = "5"
let url = URL(string: "https://example.com/users")!
let newUrl = url.appendingPathComponent(userID)
print(newUrl.absoluteString) //https://example.com/users/5
NB - url is only force unwrapped for brevity in the example, since the string passed to URL() is known to be valid. In real use, you should treat the optional more carefully.
There's something wrong with your toStringDictionary
Assuming
let fullURL = "https://demo.com/users/"
all you need to do is…
let queryParams = params.map{ URLQueryItem(name: $0, value: $1) }
var components = URLComponents(string: fullURL)!
components.queryItems = queryParams
print(components.url!)
// https://demo.com/users/?id=5
Note that you should avoid the force unwrapping in practice
I am trying to create a particular data structure as specified below in Swift.
[{"productId":1,"qty":3},{"productId":2,"qty":1},{"productId":3,"qty":5},{"productId":4,"qty":30},{"productId":5,"qty":13}]
Can some one guide me how to achive it..... I need to add and remove the data structure.
Thanks in advance.....
It is an Array of Dictionaries.
Define it like this :
var dataStructure = [[String: Any]]()
To add something :
var newData = [String: Any]()
newData["productId"] = 1
newData["qty"] = 1
dataStructure.append(newData)
To delete :
dataStructure.remove(at: indexYouWantTodeleteInInt)
It is called as dictionary in swift.
The declaration part can be as follows:
var params: [String:Any]
We can also use like:
var params: [String:Any] = ["user_id" : AppConfiguration.current.user_id]
Now to add key-value pair in it you can do as follows:
params["form_id"] = form_id!
params["parent_category_id"] = id
params["device_token"] = getDeviceToken()
params["app_version"] = APP_VERSION
params["app_device_type"] = originalDeviceType
to remove a key-value pair:
params.removeValue(forKey: "parent_category_id")
to update any value of particular key:
params.updateValue("10", forKey: "form_id")
if the above key is already present it updates the value and if not then it adds a new key to the dictionary
The Above explained part is dictionary. Now you need the data-structure as array of dictionary so you need to declare as
var params: [[String:Any]]
you can perform all the operations you can perform on an array but the value you will get at a particular index will be of type dictionary which I explained above.
Hope this helps you understand what is dictionary and what is array of dictionaries.
In your case you can also write [String: Int] instead of `[String:Any]' but it will restrict you to only have integer values with respect to the keys.
Swift developers usually use Structs in order to create a data structure from a JSON reponse. From Swift 4, JSON parsing has become very easy. Thanks to Codable protocols.
From the above given answer, you can create something like this.
MyStruct.Swift
import Foundation
typealias MyStruct = [[String: Int]]
You can then parse by calling the following method.
let myStruct = try? JSONDecoder().decode(MyStruct.self, from: jsonData)
You can add value by using this.
var newProduct = [String: Any]()
newProduct["productId"] = 941
newProduct["qty"] = 2
myStruct.append(newProduct)
To remove the data
myStruct.remove(at:"Some index")
I was thinking of using a plist file to configure how I would upload a form to my server but then I realised I don't know how I could do one crucial part of this or even if this is possible since you can't dynamically declare variables with swift.
Basically I was thinking of using a dictionary like this
form:
property_name: backend_name
and then in my form upload method I would do something that would look like this
formValues: [String:String] = [:]
form.forEach { (property_name, backend_name) in
let value = someController[property_name] // to do the equivalent of someController.property_name
formValues[backend_name] = value
}
// ...
formValues.forEach { (backend_name, value) in
multipartFormData.append(value.data(using: String.Encoding.utf8)!, withName: backend_name)
}
the problem is I don't know how to this part
let value = someController[property_name]
The problem you're trying to solve is serializing your data for a server. I don't know whether the approach you're trying is possible in Swift, but it's definitely not the right one. If you're using Swift 4, you could create Codable model objects, so this serialization will be all under the hood. Otherwise, you'll most likely need to create this [String: Any] manually.
Using Codable to serialize/deserialize objects
I found how I could do it.
formValues: [String:String] = [:]
form.forEach { (property_name, backend_name) in
let value = Mirror(reflecting: someController).children.filter({ (child) -> Bool in
return child.label == property_name
}).first!.value as! String
formValues[backend_name] = value
}
// ...
formValues.forEach { (backend_name, value) in
multipartFormData.append(value.data(using: String.Encoding.utf8)!, withName: backend_name)
}
I know this is unoptimised since I recreate a mirror each cycle but it's for demonstration purposes