Google Sheets iOS - Map documentation to SDK - ios

It is extremely difficult to map the Google Sheets V4 documentation to the SDK methods. For example:
To append a row to a Spreadsheet, the documentation states that there is a Request Resource, e.g.
"appendCells": {
object(AppendCellsRequest)
},
where AppendCellsRequest is
{
"sheetId": number,
"rows": [
{
object(RowData)
}
],
"fields": string,
}
However if I use the SDK as follows:
let range = "Sheet1"
let url = String(format:"%#/%#/values/%#", baseUrl, spreadsheetId, range)
let params = ["valueInputOption":"RAW"]
let fullUrl = GTLUtilities.URLWithString(url, queryParameters: params)
let body = GTLObject()
body.JSON = [ "appendCells" : [
"sheetId" : 0,
"rows" : [[value, value, value, value]],
"fields" : "*"
]]
driveService.fetchObjectByUpdatingObject(body, forURL: fullUrl, completionHandler: {
(ticket:GTLServiceTicket!, object:AnyObject!, error:NSError!) in
})
This fails with an error:
"Invalid JSON payload received. Unknown name "append_cells" at 'data': Cannot find field."

You seem to be calling the wrong URL. Your URL is constructed as:
let url = String(format:"%#/%#/values/%#", baseUrl, spreadsheetId, range)
That will result in:
http://sheets.googleapis.com/spreadsheets/{spreadsheet_id}/values/{range}
... but you are trying to use the AppendCellsRequest object, which isn't supported by the values collection. Each Request object in your link is describing an object that belongs in the request body of a batchUpdate call. For more information, see the Updating Spreadsheets guide.

Related

Array put request with Alamofire

I'm trying to make a put request with Alamofire and I want to pass in body something like this:
[
{
"id" : 1,
"position": 0
},
{
"id" : 2,
"position": 1
},
{
"id" : 6,
"position": 2
}
]
Normally, to do a request with alamofire I do this:
request = Alamofire
.request(
url,
method: method,
parameters: parameters,
encoding: encoding,
headers: buildHeaders());
Alamofire forces me to make parameters a dictionary but I want that paramaters to be an array of dictonary. How can I do this?
Thanks.
Alamofire added support for Encodable parameters in Alamofire 5, which provides support for Array parameters. Updating to that version will let you use Array parameters directly. This support should be automatic when passing Array parameters, you just need to make sure to use the version of request using encoder rather than encoding if you're customizing the encoding.
Well, the body of your parameters has type as [[String: Any]], or if you using Alamofire [Parameters].
So you if you parsing some Array of Objects to create this Array of parameters. You can do like this:
var positionedArray = [[String : Any]]()
for (index, item) in dataArray.enumerated() {
guard let id = item.id else {
return
}
let singleParameters : [String: Any] = ["id": id, "position" : index]
sorted.append(singleParameters)
}
As result, you can use this body (parameters), for your request.
Also, you should use JSONSerialization:
For example, if you using a customized Alamofire client, just use extension:
let data = try JSONSerialization.data(withJSONObject: parameters, options: JSONSerialization.WritingOptions.prettyPrinted)
let json = NSString(data: data, encoding: String.Encoding.utf8.rawValue)
request.httpBody = json!.data(using: String.Encoding.utf8.rawValue)
var finalRequest = try URLEncoding.default.encode(request, with: nil)

Write to Google Sheets in Swift

I'm really confused how to write to an excel sheet, that is public. So far I have downloaded Alamofire and GoogleAPIClientForREST cocoapods. So I was wondering, if I want to write to the sheet do I first need to implement a google sign in or can I just send data over using Alamofire. I am super lost so If someone can help me that would be great.
Google sheet: https://docs.google.com/spreadsheets/d/1zJR0uk6Pb6BuxJihFyxwe4ipQdBY4E9FFR74geBj8p0/edit#gid=0
func makeAndSendRequest() {
let baseUrl = "https://sheets.googleapis.com/v4/spreadsheets"
let spreadsheetId = "1zJR0uk6Pb6BuxJihFyxwe4ipQdBY4E9FFR74geBj8p0"
let params = ["valueInputOption": "RAW"]
let range = "Studen!A3:B3"
//need to add params
let url = baseUrl + "/" + spreadsheetId + "/values/" + range + "/valueInputOption=RAW/"
let fullUrl = URL(string: url)!
//my values
let requestParams = [
"values": [
1,
2
]
]
//my auth is after Bearer so "Bearer 901390"
let header = ["Authorization":"Bearer "]
let requestURL = "https://sheets.googleapis.com/v4/spreadsheets/\(spreadsheetId)/values/\(range)?valueInputOption=RAW"
let req = Alamofire.request(requestURL, method: .put, parameters: requestParams, encoding: JSONEncoding.default,headers: header)
req.responseJSON { response in debugPrint(response) }
}
Try to fix this first.
valueInputOption is a query parameter, see: https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values/update
You URL should at least be:
let url = baseUrl + "/" + spreadsheetId + "/values/" + range + "?valueInputOption=RAW"
next you need to fix your request body, range is required per document here: https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values/update
{
"range": "A3:B3",
"values": [
[
1,
2
]
]
}
This range should be same as your range in your query parameter, say Sheet1!:A3:B3 as in your query parameter.
Note that it starts with the sheet name, it should not be "studen"
I have used the playground tool on the same page and able to call the api to change the data in your spreadsheet.
At the end I am getting 200 response code and following json:
{
"spreadsheetId": "1zJR0uk6Pb6BuxJihFyxwe4ipQdBY4E9FFR74geBj8p0",
"updatedRange": "Sheet1!A3:B3",
"updatedRows": 1,
"updatedColumns": 2,
"updatedCells": 2
}
----- Edit -----
As you asked, the API needs Oauth token, the framework to handle this is actually called G Suite
Please find the guide here:
https://developers.google.com/gsuite/guides/ios

Sending array of dictionaries with alamofire

I have to send array of dictionaries via POST request. For example:
materials: [[String, String]] = [
[
"material_id": 1,
"qty": 10
],
[
"material_id": 2,
"qty": 5
]
]
Alamofire.request sends the next post data:
materials => array(
[0] => array("material_id" => 1),
[1] => array("qty" => 10),
[2] => array("material_id" => 2),
[3] => array("qty" => 5),
)
I want receive that representation:
materials => array(
[0] => array(
"material_id" => 1,
"qty" => 10
),
[1] => array(
"material_id" => 2,
"qty" => 5
),
)
The problem was in append method. I have coded on PHP 5 years and forgoted that in Swift the indexes not automatically assigned like in PHP. So, my first bugged code was:
func getParameters() -> [[String: AnyObject]] {
var result = [[String: AnyObject]]()
for mmap in mmaps {
let material: [String: AnyObject] = [
"material_id": mmap.material.id,
"quantity": mmap.qty
]
result.append(material)
}
return result
}
The answer is hard assign the keys as you need:
func getParameters() -> [String: [String: AnyObject]] {
var result = [String: [String: AnyObject]]()
let mmaps = self.mmaps.allObjects as [Mmap]
for i in 0..<mmaps.count {
let mmap = mmaps[i]
let material: [String: AnyObject] = [
"material_id": mmap.material.id,
"quantity": mmap.qty
]
result["\(i)"] = material
}
return result
}
A couple of thoughts:
It would be easiest if you sent the response as a dictionary with one key, and it will correctly encode the array within the dictionary:
let materials = [ "materials":
[
[
"material_id": 1,
"qty": 10
],
[
"material_id": 2,
"qty": 5
]
]
]
You could then just supply that as the parameters of request(), and Alamofire will properly encode that for you.
If you wanted to send an array of dictionaries, an alternative would be to change the web service to accept JSON. You could then encode the JSON yourself (using JSONSerialization or JSONEncoder), set the body of the request, and then send that request.
If you want to send application/x-www-form-urlencoded request with the array of dictionaries, you'd have to encode that yourself. In Swift 3 and later, that might look like:
func encodeParameters(_ object: Any, prefix: String? = nil) -> String {
if let dictionary = object as? [String: Any] {
return dictionary.map { key, value -> String in
self.encodeParameters(value, prefix: prefix != nil ? "\(prefix!)[\(key)]" : key)
}.joined(separator: "&")
} else if let array = object as? [Any] {
return array.enumerated().map { (index, value) -> String in
return self.encodeParameters(value, prefix: prefix != nil ? "\(prefix!)[\(index)]" : "\(index)")
}.joined(separator: "&")
} else {
let escapedValue = "\(object)".addingPercentEncoding(withAllowedCharacters: .urlQueryValueAllowed)!
return prefix != nil ? "\(prefix!)=\(escapedValue)" : "\(escapedValue)"
}
}
Where
extension CharacterSet {
/// Returns the character set for characters allowed in the individual parameters within a query URL component.
///
/// The query component of a URL is the component immediately following a question mark (?).
/// For example, in the URL `http://www.example.com/index.php?key1=value1#jumpLink`, the query
/// component is `key1=value1`. The individual parameters of that query would be the key `key1`
/// and its associated value `value1`.
///
/// According to RFC 3986, the set of unreserved characters includes
///
/// `ALPHA / DIGIT / "-" / "." / "_" / "~"`
///
/// In section 3.4 of the RFC, it further recommends adding `/` and `?` to the list of unescaped characters
/// for the sake of compatibility with some erroneous implementations, so this routine also allows those
/// to pass unescaped.
static var urlQueryValueAllowed: CharacterSet = {
let generalDelimitersToEncode = ":#[]#" // does not include "?" or "/" due to RFC 3986 - Section 3.4
let subDelimitersToEncode = "!$&'()*+,;="
var allowed = CharacterSet.urlQueryAllowed
allowed.remove(charactersIn: generalDelimitersToEncode + subDelimitersToEncode)
return allowed
}()
}
Obviously, use whatever response method is appropriate for the nature of your server's response (e.g. response vs. responseJSON vs. ...).
Anyway, the above generates a request body that looks like:
materials[0][material_id]=1&materials[0][qty]=10&materials[1][material_id]=2&materials[1][qty]=5
And this appears to be parsed by servers as you requested in your question.
It's worth noting that this final point illustrates the preparation of an application/x-www-form-urlencoded request with nested dictionary/array structure, as contemplated here. This works on my server run by a major ISP, but I must confess that I haven't seen this convention documented in formal RFCs, so I'd be wary of doing it. I'd personally be inclined to implement this as JSON interface.
For prior versions of Swift, see previous revision of this answer.
You can make your array as JSON string and post in to server, then parse JSON in server end and from that you can get your desired data, lke this:
NSError *error;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject: yourArry options:NSJSONWritingPrettyPrinted error:&error];
NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
Hope this helps.. :)

Getting query string parameters from url in the UIWebView in Swift?

my question is pretty straight forward. Can someone please show me how to get the query string parameters in a url received by the UIWebView. It is pretty easy in objective c, but I need some help in swift as i'm new to the language. Thanks in advance.
In the NSURL class exists the .query property which returns the string of everything after the ? in the url in form:
http://www.example.com/index.php?key1=value1&key2=value2
in this example using the code:
var url: NSURL = NSURL(string: "http://www.example.com/index.php?key1=value1&key2=value2")
println(url.query) // Prints: Optional("key1=value1&key2=value2")
More information in the documentation
As far as getting the url from the uiwebview, you could use something along the lines of:
let url: NSURL = someWebView.NSURLRequest.URL
To make it a bit easier to get the value of a particular parameter you expect, you can use URLComponents which will parse out the query string parameters for you.
For example, if we want the value of the query string parameter key2 in this URL:
"https://www.example.com/path/to/resource?key1=value1&key2=value2"
We can create a URLComponents struct, then filter for the query item which matches the name, take the first one, and print its value:
let url = URL(string: "https://www.example.com/path/to/resource?key1=value1&key2=value2")
if let url = url,
let urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false) {
let parameterWeWant = urlComponents.queryItems?.filter({ $0.name == "key2" }).first
print(parameterWeWant?.value ?? "")
}
The key (!) thing is that urlComponents.queryItems gives us this array of QueryItem structs back, which gives us an easier way to filter the parameters and get the value of the parameter we're looking for.
▿ Optional([key1=value1, key2=value2])
▿ some: 2 elements
▿ key1=value1
- name: "key1"
▿ value: Optional("value1")
- some: "value1"
▿ key2=value2
- name: "key2"
▿ value: Optional("value2")
- some: "value2"
We can parse the Url params with this method,
func getParameterFrom(url: String, param: String) -> String? {
guard let url = URLComponents(string: url) else { return nil }
return url.queryItems?.first(where: { $0.name == param })?.value
}
let url = "http://www.example.com/index.php?key1=value1&key2=value2"
let key1 = self.getParameterFrom(url: url, param: "key1")
print("\(key1)") // value1

Parsing JSON Response with Alamofire in Swift

When using the Alamofire Framework, my responses don't seem to be getting parsed correctly. The JSON response I get has some keys that appear to not be strings, and I don't know how to reference them/get their values.
Here is the part of my code that makes the call:
var url = "http://api.sandbox.amadeus.com/v1.2/flights/low-fare-search"
var params = ["origin": "IST",
"destination":"BOS",
"departure_date":"2014-10-15",
"number_of_results": 1,
"apikey": KEY]
Alamofire.request(.GET, url, parameters: params)
.responseJSON { (_, _, json, _) in
println(json)
}
}
And here is the first section printout when that function is called
Optional({
currency = USD;
results = ({
fare = {
"price_per_adult" = {
tax = "245.43";
"total_fare" = "721.43";
};
restrictions = {
"change_penalties" = 1;
refundable = 0;
};
"total_price" = "721.43";
};
...
});
});
You'll notice that results is not "results", but "price_per_adult" is the correct format. Is there some step I'm missing? When I cast it to NSDictionary it doesn't do anything to help the key format either.
I also tried the same endpoint in javascript and ruby, and both came back without problem, so I'm fairly confident that it is not the API that is causing problems.
Those keys are still Strings, that's just how Dictionarys are printlnd. It looks like it will surround the String in quotes when printing it only if it contains non-alphanumeric characters (_ in this case). You can test this by manually creating a Dictionary similar to the one you're getting back from your API request and then printing it:
let test = [
"currency": "USD",
"results": [
[
"fare": [
"price_per_adult": [
"tax": "245.43",
"total_fare": "721.43"
],
"restrictions": [
"change_penalties": 1,
"refundable": 0
],
"total_price": "721.43"
]
]
]
]
println(test)
Outputs:
{
currency = USD;
results = (
{
fare = {
"price_per_adult" = {
tax = "245.43";
"total_fare" = "721.43";
};
restrictions = {
"change_penalties" = 1;
refundable = 0;
};
"total_price" = "721.43";
};
}
);
}

Resources