I try to get NSString from NSURL with this method:
NSString *tmp2 = [item.path absoluteString];
Unfortunately I get instead of the NSURL:
<CFURL 0x173c50 [0x3f1359f8]>{type = 0, string = /var/mobile/Applications/A30FD2E4-A273-4522-AFD5-A981EFD3C2AA/Documents/*** *** - *** ***.***, encoding = 134217984, base = (null)}
I get :
file://localhost/var/mobile/Applications/A30FD2E4-A273-4522-AFD5-A981EFD3C2AA/Documents/***%20***%20-%20***%20***.***
any idea why?
The NSURL documentation clearly states that absoluteString returns an NSString, just like your code above. This is the string representation of the absolute path, so what you are getting is what you should be getting.
However, looking at the documentation you could also use path, relativePath or relativeString to get a string representation of the url in other formats (absolute or relative paths that either do or do not conform to RFC 1808 (a now obsolete percent encoding).
Related
I'm trying to get an understanding of some code I came across recently.
In an answer to a question here https://stackoverflow.com/a/51173170/1162328, the author made use of a String with a format specifier when looping over files in the documentDirectory. Can anyone shed some light on what %#/%# is actually doing?
for fileName in fileNames {
let tempPath = String(format: "%#/%#", path, fileName)
// Check for specific file which you don't want to delete. For me .sqlite files
if !tempPath.contains(".sql") {
try fileManager.removeItem(atPath: tempPath)
}
}
Reading the Apple documentation archive for Formatting Basics I came across this:
In format strings, a ‘%’ character announces a placeholder for a value, with the characters that follow determining the kind of value expected and how to format it. For example, a format string of "%d houses" expects an integer value to be substituted for the format expression '%d'. NSString supports the format characters defined for the ANSI C functionprintf(), plus ‘#’ for any object.
What exactly then, is %#/%# doing?
Each format specifier is replaced by one of the following arguments (usually in the same order, although that can be controlled with positional arguments). So in your case, the first %# is replaced by path and the second %# is replaced by fileName. Example:
let path = "/path/to/dir"
let fileName = "foo.txt"
let tempPath = String(format: "%#/%#", path, fileName)
print(tempPath) // /path/to/dir/foo.txt
The preferred way to build file names and paths is to use the corresponding URL methods instead of string manipulation. Example:
let pathURL = URL(fileURLWithPath: path)
let tempURL = pathURL.appendingPathComponent(fileName)
if tempURL.pathExtension != "sql" {
try FileManager.default.removeItem(at: tempURL)
}
%# is something similar to %d or anything like that. This is the way of string interpolation in Swift.
To be exact %# is placeholder for object - used in Objective-C A LOT. Since NSString * was object (now it is only String), it was used to insert NSString * into another NSString *.
Also given code is just rewritten objective-c code which was something like
NSString *tempPath = [NSString stringWithFormat:#"%#/%#", path, filename];
which can be rewritten in swift:
let tempPath = path + "/" + fileName
Also, given path = "Test" and fileName = "great" will give output Test/great.
One more note: %# is as good as dangerous. You can put UITableView as well as String in it. It will use description property for inserting into string.
My code:
func testApi() {
Alamofire.request("https://www.poloniex.com/tradingApi", withMethod: .post, parameters: ["command":"returnDepositAddresses","nonce":nonce()], encoding: .json, headers: ["Key":apiKey,"Sign":newSecret]).responseJSON() { (dataBack) in
print(dataBack)
}
}
func nonce() -> Int {
let date = "\(NSDate().timeIntervalSince1970)"
let UnixInt = Double(date)!
return Int(UnixInt)
}
And I get it:
SUCCESS: {
error = "Invalid command.";}
I can't find any info about poloniex api with Swift or Objective C...
So if somebody can help - I'll be very grateful
Here is an example of how to form your NSURLRequest for poloniex.com.
Imagine that your:
API Key = #"apikey"
Secret = #"secret"
nonce = #"1"
Starting with the simplest things:
NSMutableURLRequest *theURLRequest = [NSMutableURLRequest new];
theURLRequest.URL = [NSURL URLWithString:#"https://poloniex.com/tradingApi"];
theURLRequest.HTTPMethod = #"POST";
NSString *theBodyString = #"command=returnBalances&nonce=1";
theURLRequest.HTTPBody = [theBodyString dataUsingEncoding:NSUTF8StringEncoding];
[theURLRequest setValue:#"apikey" forHTTPHeaderField:#"Key"];
And now the hardest bit...
As to me, Poloniex documentation wasn't very clear on what they want under the "Sign" header field value, but basically they want you to pass a string, which should be a result of HMAC SHA512 encryption algorithm applied to both theBodyString and Secret (which in our example is simply #"secret").
Here is the function which would return you the HMAC SHA512 NSData:
#import <CommonCrypto/CommonHMAC.h>
NSData * getHMACSHA512FromSecretKeyStringAndBodyString(NSString *theSecretKeyString, NSString *theBodyString)
{
const char *cSecret = [theSecretKeyString cStringUsingEncoding:NSUTF8StringEncoding];
const char *cBody = [theBodyString cStringUsingEncoding:NSUTF8StringEncoding];
unsigned char cHMAC[CC_SHA512_DIGEST_LENGTH];
CCHmac(kCCHmacAlgSHA512, cSecret, strlen(cSecret), cBody, strlen(cBody), cHMAC);
return [[NSData alloc] initWithBytes:cHMAC length:sizeof(cHMAC)];
}
So, running:
NSData *theData = getHMACSHA512FromSecretKeyStringAndBodyString(#"secret", #"command=returnBalances&nonce=1");
NSString *theString = [NSString stringWithFormat:#"%#", theData];
Would give us almost what we wanted.
Our result is equal to:
<c288f881 a6808d0e 78827ec6 ca9d6b9c 34ec1667 07716303 0d6d7abb 2b225456 31176f52 8347ab0f d6671ec5 3aec1f7d 3b6de8b8 e3ccc23d e62fd594 52d70db5>
While what we actually want (as per http://www.freeformatter.com/hmac-generator.html) is:
c288f881a6808d0e78827ec6ca9d6b9c34ec1667077163030d6d7abb2b22545631176f528347ab0fd6671ec53aec1f7d3b6de8b8e3ccc23de62fd59452d70db5
So, basically, just remove the <, > and symbols from your string;
theString = [theString stringByReplacingOccurrencesOfString:#"<" withString:#""];
theString = [theString stringByReplacingOccurrencesOfString:#">" withString:#""];
theString = [theString stringByReplacingOccurrencesOfString:#" " withString:#""];
[theURLRequest setValue:theString forHTTPHeaderField:#"Sign"];
Your theURLRequest is now ready and should succeed getting the tradingApi of poloniex.com.
Actually it's neither Swift nor iOS issue.
It's because you are accessing Trading API methods, and they may require some more additional parameters (except of nonce) in your POST request:
Check this:
All calls to the trading API are sent via HTTP POST to
https://poloniex.com/tradingApi and must contain the following
headers:
Key - Your API key. Sign - The query's POST data signed by your key's
"secret" according to the HMAC-SHA512 method. Additionally, all
queries must include a "nonce" POST parameter. The nonce parameter is
an integer which must always be greater than the previous nonce used.
Thus:
All responses from the trading API are in JSON format. In the event of
an error, the response will always be of the following format:
{"error":""}
https://temp.poloniex.com/support/api/
I'm using NSURLComponents and I can't seem to get the query values to encode correctly. I need the final URL to represent a + as %2B.
let baseUrl = NSURL(string: "http://www.example.com")
let components = NSURLComponents(URL: baseUrl, resolvingAgainstBaseURL: true)
components.queryItems = [ NSURLQueryItem(name: "name", value: "abc+def") ]
XCTAssertEqual(components!.string!, "http://www.example.com?connectionToken=abc%2Bdef")
Failed!
Output equals:
http://www.example.com?connectionToken=abc+def
NOT
http://www.example.com?connectionToken=abc%2Bdef
I've tried several variations and I just can't seem to get it to output %2B at all.
My answer from Radar 24076063 with an explanation of why it works the way it does (with a little cleanup of the text):
The '+' character is legal in the query component so it does not need to be percent-encoded.
Some systems use the '+' as a space and require '+' the plus character to be percent-encoded. However, that kind of two stage encoding (converting plus sign to %2B and then converting space to plus sign) is prone to errors because it easily leads to encoding problems. It also breaks if the URL is normalized (syntax normalization of URLs includes the removal of all unnecessary percent-encoding — see rfc3986 section 6.2.2.2).
So, if you need that behavior because of the server your code is talking to, you'll handle the extra transformation(s) yourself. Here's a snippet of code that shows what you need to do both ways:
NSURLComponents *components = [[NSURLComponents alloc] init];
NSArray *items = [NSArray arrayWithObjects:[NSURLQueryItem queryItemWithName:#"name" value:#"Value +"], nil];
components.queryItems = items;
NSLog(#"URL queryItems: %#", [components queryItems]);
NSLog(#"URL string before: %#", [components string]);
// Replace all "+" in the percentEncodedQuery with "%2B" (a percent-encoded +) and then replace all "%20" (a percent-encoded space) with "+"
components.percentEncodedQuery = [[components.percentEncodedQuery stringByReplacingOccurrencesOfString:#"+" withString:#"%2B"] stringByReplacingOccurrencesOfString:#"%20" withString:#"+"];
NSLog(#"URL string after: %#", [components string]);
// This is the reverse if you receive a URL with a query in that form and want to parse it with queryItems
components.percentEncodedQuery = [[components.percentEncodedQuery stringByReplacingOccurrencesOfString:#"+" withString:#"%20"] stringByReplacingOccurrencesOfString:#"%2B" withString:#"+"];
NSLog(#"URL string back: %#", [components string]);
NSLog(#"URL queryItems: %#", [components queryItems]);
The output is:
URL queryItems: (
"<NSURLQueryItem 0x100502460> {name = name, value = Value +}"
)
URL string before: ?name=Value%20+
URL string after: ?name=Value+%2B
URL string back: ?name=Value%20+
URL queryItems: (
"<NSURLQueryItem 0x1002073e0> {name = name, value = Value +}"
)
As the other answers mention, "+" isn't encoded on iOS by default. But if your server requires that to be encoded, here's how to do it:
var comps = URLComponents(url: self, resolvingAgainstBaseURL: true)
// a local var is needed to fix a swift warning about "overlapping accesses" caused by writing to the same property that's being read.
var compsCopy = comps
compsCopy?.queryItems = [URLQueryItem(name: "name", value: "abc+def")]
comps?.percentEncodedQuery = compsCopy?.percentEncodedQuery?.replacingOccurrences(of: "+", with: "%2B")
+ may be a valid character when the content-type is application/x-www-form-urlencoded, see the link, so NSURLComponents doesn't encode it.
Apple also mention this:
RFC 3986 specifies which characters must be percent-encoded in the
query component of a URL, but not how those characters should be
interpreted. The use of delimited key-value pairs is a common
convention, but isn't standardized by a specification. Therefore, you
may encounter interoperability problems with other implementations
that follow this convention.
One notable example of potential interoperability problems is how the
plus sign (+) character is handled:
According to RFC 3986, the plus sign is a valid character within a
query, and doesn't need to be percent-encoded. However, according to
the W3C recommendations for URI addressing, the plus sign is reserved
as shorthand notation for a space within a query string (for example,
?greeting=hello+world).
If a URL query component contains a date formatted according to RFC
3339 with a plus sign in the timezone offset (for example,
2013-12-31T14:00:00+00:00), interpreting the plus sign as a space
results in an invalid time format. RFC 3339 specifies how dates should
be formatted, but doesn't advise whether the plus sign must be
percent-encoded in a URL. Depending on the implementation receiving
this URL, you may need to preemptively percent-encode the plus sign
character.
As an alternative, consider encoding complex and/or potentially
problematic data in a more robust data-interchange format, such as
JSON or XML.
The conclude is you may or may not encode '+'.
In my opinion, NSURLComponents only encode character which it make sure that should be encoded, such as '&', '=' or Chinese characters like '你' '好', it doesn't encode the character may be encode or not according to the content-type, like '+' I mentioned above. So if you find you have to encode '+' or your server can't parse correctly, you can use the code below.
I don't know swift, so I just provide objective-c code, sorry for that.
- (NSString *)URLEncodingValue:(NSString *)value
{
NSCharacterSet *set = [NSCharacterSet URLQueryAllowedCharacterSet];
NSMutableCharacterSet *mutableQueryAllowedCharacterSet = [set mutableCopy];
[mutableQueryAllowedCharacterSet removeCharactersInString:#"!*'();:#&=+$,/?%#[]"];
return [value stringByAddingPercentEncodingWithAllowedCharacters:mutableQueryAllowedCharacterSet];
}
!*'();:#&=+$,/?%#[] are reserved characters defined in RFC 3986, the code will encode all of them appear in the value parameter, if you just want to encode '+', just replace !*'();:#&=+$,/?%#[] with +.
This is an Apple bug. Instead use
NSString -stringByAddingPercentEncodingWithAllowedCharacters:
with
NSCharacterSet +URLQueryAllowedCharacterSet
I am getting the following response
param=%7B%22paymentMode%22:%22%22,%22transactionId%22:%2231674%22,%22pgRespCode%22:%223%22,%22TxMsg%22:%22Canceled%20by%20user%22,%22authIdCode%22:%22%22,%22currency%22:%22INR%22,%22amount%22:%221.00%22,%22addressStreet1%22:%22Sesame%20street%22,%22addressStreet2%22:%22%22,%22isCOD%22:%22%22,%22loadStatus%22:%22fail%22,%22TxId%22:%22123456%22,%22addressCountry%22:%22India%22,%22firstName%22:%22Ankur%22,%22TxGateway%22:%22%22,%22signature%22:%2245558eb93513aa7a4f2fba24e0ba577b26eb5f40%22,%22addressState%22:%22Pune%22,%22lastName%22:%22Arya%22,%22addressCity%22:%22%22,%22TxRefNo%22:%22CTX1307151506338704178%22,%22loadAmount%22:%221.00%20INR%22,%22pgTxnNo%22:%22CTX1307151506338704178%22,%22TxStatus%22:%22CANCELED%22,%22email%22:%22daredevil.suyash#gmail.com%22,%22issuerRefNo%22:%22%22,%22mobileNo%22:%229900414420%22,%22addressZip%22:%22411045%22%7D
how can I decode it into the following
param={"paymentMode":"","transactionId":"31674","pgRespCode":"3","TxMsg":"Canceled by user","authIdCode":"","currency":"INR","amount":"1.00","addressStreet1":"Sesame street","addressStreet2":"","isCOD":"","loadStatus":"fail","TxId":"123456","addressCountry":"India","firstName":"Ankur","TxGateway":"","signature":"45558eb93513aa7a4f2fba24e0ba577b26eb5f40","addressState":"Pune","lastName":"Arya","addressCity":"","TxRefNo":"CTX1307151506338704178","loadAmount":"1.00 INR","pgTxnNo":"CTX1307151506338704178","TxStatus":"CANCELED","email":"daredevil.suyash#gmail.com","issuerRefNo":"","mobileNo":"9900414420","addressZip":"411045"}
You can try this method stringByReplacingPercentEscapesUsingEncoding:NSStringEncodingConversionAllowLossy]
Taken from this question urldecode in objective-c
//
NSString *param=#"%7B%22paymentMode%22:%22%22,%22transactionId%22:%2231674%22,%22pgRespCode%22:%223%22,%22TxMsg%22:%22Canceled%20by%20user%22,%22authIdCode%22:%22%22,%22currency%22:%22INR%22,%22amount%22:%221.00%22,%22addressStreet1%22:%22Sesame%20street%22,%22addressStreet2%22:%22%22,%22isCOD%22:%22%22,%22loadStatus%22:%22fail%22,%22TxId%22:%22123456%22,%22addressCountry%22:%22India%22,%22firstName%22:%22Ankur%22,%22TxGateway%22:%22%22,%22signature%22:%2245558eb93513aa7a4f2fba24e0ba577b26eb5f40%22,%22addressState%22:%22Pune%22,%22lastName%22:%22Arya%22,%22addressCity%22:%22%22,%22TxRefNo%22:%22CTX1307151506338704178%22,%22loadAmount%22:%221.00%20INR%22,%22pgTxnNo%22:%22CTX1307151506338704178%22,%22TxStatus%22:%22CANCELED%22,%22email%22:%22daredevil.suyash#gmail.com%22,%22issuerRefNo%22:%22%22,%22mobileNo%22:%229900414420%22,%22addressZip%22:%22411045%22%7D";
NSString *newParam = [param stringByReplacingPercentEscapesUsingEncoding:NSStringEncodingConversionAllowLossy];
NSLog(#"%#",newParam);
Do string replacement stuff with that to get it into a more readable form.
string = [string stringByReplacingOccurrencesOfString: #"%%22" withString:#"\""];
string = [string stringByReplacingOccurrencesOfString: #"%%7B" withString:#"{"];
string = [string stringByReplacingOccurrencesOfString: #"%%7D" withString:#"}"];
And so on, until you get it into something you want.
You are basically replacing the unicode representation of the character into the actual readable character
How do I use NSLocalizedString in this case when I have a header where I define a few parameters, say:
#define appKey #"appKey1 is: %#"
I think I know that my Localizable.strings should look like that:
"blabla" = "appKey1 is: %#"
but how do I use NSLocalizedString? I read that I need to use stringWithFormat, but not sure how...
thanks!
You would define your constant as:
#define appKey NSLocalizedString(#"appKey1 is: %#", #"appkey constant")
Then it should get picked up by the genstrings tool in the usual way.
In the strings file it would then come out like this:
/* appkey constant */
"appKey1 is: %#" = "appKey1 is: %#";
And you would translate just the right hand side.
String literals are acceptable in NSLocalizedStrings. What you need to do is something like
#define appKey NSLocalizedString(BlahBlah , comments);
"BlahBlah" = "appKey1 is: %#";
(Be sure to end your lines with a semi-colon in Localizable.strings, or it will end up being corrupted).
This is how you would do it normally,
NSString * myString = [NSString stringWithFormat:#"appKey1 is: %#",yourAppKeyString];
Since you have it defined you can use it like so
NSString * myString = [NSString stringWithFormat:appKey,yourAppKeyString];
Either case both would fill your myString like so
yourAppKeyString = #"keyString";
myString = #"appKey1 is: keyString";
NSString * myString = [NSString stringWithFormat: NSLocalizedString(#"appKey", #""),yourAppKeyString];