ios URL(string: ) not working always - ios

let testurl = "http://akns-
images.eonline.com/eol_images/Entire_Site/2018029/rs_1024x759-
180129200032-1024.lupita-nyongo-angela-bassett-black-panther-
premiere.ct.012918.jpg?fit=inside|900:auto"
if let url = URL(string: testurl){
print("valid")
}else {
print("invalid")
}
This prints as an invalid URL. But shows the image in web browser. I have seen lot of methods but it throws Apple warning to use stringByAddingPercentEncodingWithAllowedCharacters and this doesn't work always.
I would prefer a solution to clean-up the url without encoding the complete urlstring. Encoding it at initial step can be a friction when passing to external libraries which will re-encode the url and make it invalid.

Use URLComponents, it will handle escape characters automatically and encodes the url correct. no need to use addingPercentEncoding
func computeURL() {
let testurlStr = "http://akns-images.eonline.com/eol_images/Entire_Site/2018029/rs_1024x759-180129200032-1024.lupita-nyongo-angela-bassett-black-panther-premiere.ct.012918.jpg?fit=inside|900:auto"
let components = transformURLString(testurlStr)
if let url = components?.url {
print("valid")
} else {
print("invalid")
}
}
func transformURLString(_ string: String) -> URLComponents? {
guard let urlPath = string.components(separatedBy: "?").first else {
return nil
}
var components = URLComponents(string: urlPath)
if let queryString = string.components(separatedBy: "?").last {
components?.queryItems = []
let queryItems = queryString.components(separatedBy: "&")
for queryItem in queryItems {
guard let itemName = queryItem.components(separatedBy: "=").first,
let itemValue = queryItem.components(separatedBy: "=").last else {
continue
}
components?.queryItems?.append(URLQueryItem(name: itemName, value: itemValue))
}
}
return components!
}

URL encoding is compulsory for building a URL from a string in case it contains special characters.
Why we need URL encoding
From this answer:
A URI is represented as a sequence of characters, not as a sequence of octets. That is because URI might be "transported" by means that are not through a computer network, e.g., printed on paper, read over the radio, etc.
And
For original character sequences that contain non-ASCII characters, however, the situation is more difficult. Internet protocols that transmit octet sequences intended to represent character sequences are expected to provide some way of identifying the charset used, if there might be more than one [RFC2277]. However, there is currently no provision within the generic URI syntax to accomplish this identification. An individual URI scheme may require a single charset, define a default charset, or provide a way to indicate the charset used.
In the IOS SDK we have the following:
User: URLUserAllowedCharacterSet
Password: URLPasswordAllowedCharacterSet
Host: URLHostAllowedCharacterSet
Path: URLPathAllowedCharacterSet
Fragment: URLFragmentAllowedCharacterSet
Query: URLQueryAllowedCharacterSet
You can use addingPercentEncoding(withAllowedCharacters:) to encode a string correctly.
One important note according to the apple docs for that method:
Entire URL strings cannot be percent-encoded, because each URL component specifies a different set of allowed characters. For example, the query component of a URL allows the “#” character, but that character must be percent-encoded in the password component.
UTF-8 encoding is used to determine the correct percent-encoded characters. Any characters in allowedCharacters outside of the 7-bit ASCII range are ignored.
An example:
let encodedURL = testurl.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)
if let url = URL(string: finalUrl) {
print("valid url")
} else {
print("invalid url")
}
I hope this is helpful.

I think I have found my answer. Still will be checking for any side effects, comments are welcome:
let urlst = "http://akns-
images.eonline.com/eol_images/Entire_Site/2018029/rs_1024x759-
180129200032-1024.lupita-nyongo-angela-bassett-black-panther-
premiere.ct.012918.jpg?fit=inside|900:auto"
extension String {
func getCleanedURL() -> URL? {
guard self.isEmpty == false else {
return nil
}
if let url = URL(string: self) {
return url
} else {
if let urlEscapedString = self.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed) , let escapedURL = URL(string: urlEscapedString){
return escapedURL
}
}
return nil
}
}
This does not encode the complete URL but encodes only the invalid query characters (in eg url its '|'). Thus it can still be passed around as a string or a valid URL.

Based on your own answer, I would even simplify it further by making it an extension of URL instead of String, that way we can still use an initialiser which is similar to URL(string:) with an identical output:
extension URL {
init?(_ string: String) {
guard string.isEmpty == false else {
return nil
}
if let url = URL(string: string) {
self = url
} else if let urlEscapedString = string.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed),
let escapedURL = URL(string: urlEscapedString) {
self = escapedURL
} else {
return nil
}
}
}

Please replace your line with:
let finalUrl = testurl.addingPercentEncoding( withAllowedCharacters: .urlUserAllowed)
if let url = URL(string: finalUrl){
print("valid")
} else {
print("invalid")
}

Related

Gmail not removing percent encoding while creating a pre-populated email url in swift

I am trying to add a suggestion/feedback section in my app. It's supposed to open a mail app with pre-populated text for the subject, body, and email address. It's working fine except the subject and body show the percent-encoding for space. I have searched a lot and it seems like an iOS 15 issue but I am not sure. This is my code:
private func createEmailUrl(to: String, subject: String, body: String) -> URL? {
let subjectEncoded = subject.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!
let bodyEncoded = body.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!
let gmailUrl = URL(string: "googlegmail://co?to=\(to)&subject=\(subjectEncoded)&body=\(bodyEncoded)")
let outlookUrl = URL(string: "ms-outlook://compose?to=\(to)&subject=\(subjectEncoded)")
let yahooMail = URL(string: "ymail://mail/compose?to=\(to)&subject=\(subjectEncoded)&body=\(bodyEncoded)")
let sparkUrl = URL(string: "readdle-spark://compose?recipient=\(to)&subject=\(subjectEncoded)&body=\(bodyEncoded)")
let defaultUrl = URL(string: "mailto:\(to)?subject=\(subjectEncoded)&body=\(bodyEncoded)")
if let gmailUrl = gmailUrl, UIApplication.shared.canOpenURL(gmailUrl) {
return gmailUrl
} else if let outlookUrl = outlookUrl, UIApplication.shared.canOpenURL(outlookUrl) {
return outlookUrl
} else if let yahooMail = yahooMail, UIApplication.shared.canOpenURL(yahooMail) {
return yahooMail
} else if let sparkUrl = sparkUrl, UIApplication.shared.canOpenURL(sparkUrl) {
return sparkUrl
}
return defaultUrl
}
And this is how I am calling the API
UIApplication.shared.open(url)
The Gmail app shows the subject as:
Reg:%2520Suggestions/Feedback%2520About%2520iOS%2520App
It wasn't an issue at all. I was sending an already encoded string and it was encoding % to %25. Hence all my encoded special characters were converted into %25__.
My bad. I must read the code properly before copy-pasting.

URL Encoding swift IOS for random URLs received

As I don't have enough rep points for comment for this question URL Encoding swift IOS, I created a new question. I would like to receive feedback on making it more swifty for use cases like this one.
My understanding of OP question is inside API response or from data, there is multiple links of URLs for image, with possibility of different host domains.
In such cases, the host URL could be first retrieved like in this answer. In similar way, the path can also be retrieved separately.
The path can be encoded like as below and concat back to host string later.
addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)
This is what I initially tried out in playground.
import Foundation
extension String {
var encoded: String? {
return self.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed)
}
}
let urlStrings:[String] = ["https://example.com/test.jpg"
"https://example.cc/test2.jpg",
"https://example.dev.cc/test3.jpg"]
var encodedUrlStrings: [String] = []
urlStrings.forEach { urlString in
print(urlString)
guard let url = URL(string: urlString),
let domain = url.host,
let encodedPath = url.path.encoded else { return }
let encodedUrlString = domain + encodedPath
encodedUrlStrings.append(encodedUrlString)
}
encodedUrlStrings.count
encodedUrlStrings.forEach { print($0) }
But the weird thing I notice from OP's question is the url itself should not be already including character like space or any unicode character.
https://ftximages.apk.in/test Fretailer_Point-6685.jpg
For such string, converting into URL will always return nil with URL(String:) method. Same result for using URLComponents as well as the characters in path are still not usable in URL. So this need to make more agreements with backedn dev or anyone who's sending such URL string without encoded characters.
Just in case the backend dev is making a fuss, following codes will gives example of encoded URL strings.
String extension from #Claudio's answer.
import Foundation
extension String {
var encoded: String? {
return self.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed)
}
}
let urlStrings:[String] = ["https://example.com.cc/folder/test.jpg",
"https://example.com/test.jpg",
"https://Ftximages.apk.in/test Fretailer_Point-6685.jpg",
"https://example.com/원 더 우먼.jpg",
"https://example.dev.cc/刺客聶隱娘-6685.jpg",
"https://example.cc/बाजीराव मस्तानी.jpg"]
var encodedUrlStrings: [String] = []
func encodeURL(scheme: String, host: String, path: String) -> String? {
var components = URLComponents()
components.scheme = scheme
components.host = host
components.path = path
return components.url?.absoluteString ?? URL(string: "\(scheme)://\(host)/\(path)")?.absoluteString
}
func encodeWeirdUrlString(_ urlString: String) -> String? {
var urlSplitStrings = urlString.components(separatedBy: ["/"])
urlSplitStrings.removeAll(where: { $0.isEmpty })
guard urlSplitStrings.indices.contains(0), urlSplitStrings.indices.contains(1) else { return nil }
let scheme = String(urlSplitStrings[0].dropLast())
let host = urlSplitStrings[1]
urlSplitStrings.remove(at: 1)
urlSplitStrings.remove(at: 0)
guard let path = urlSplitStrings.joined(separator: "/").encoded else { return nil}
let encodedUrlString = encodeURL(scheme: scheme, host: host, path: path)
return encodedUrlString
}
urlStrings.forEach { urlString in
print(urlString)
guard let url = URL(string: urlString),
let scheme = url.scheme,
let host = url.host,
let encodedPath = url.path.encoded,
let encodedUrlString = encodeURL(scheme: scheme, host: host, path: encodedPath) else {
if let encodedUrlString = encodeWeirdUrlString(urlString) {
encodedUrlStrings.append(encodedUrlString)
}
return
}
encodedUrlStrings.append(encodedUrlString)
}
encodedUrlStrings.count
encodedUrlStrings.forEach { print($0) }
Are there any improvements to make this solution more swifty?

When passing the valid url of the string with the init the URL(string: urlString) returns nil. How to fix?

When passing the valid url of the string with the init the URL(string: urlString) returns nil. How to fix? When I enter a value from one word (for example, Moscow) everything works, when a city from two words is entered, I separate the request using split(separator: " ") and connect using .joined(separator: "%20") and get the city divided by %20 and return nil. How can this problem be solved?
enter image description here
enter image description here
You need to do addingPercentEncoding see my extension for that
extension String {
var url: URL? {
guard !isEmpty else { return nil }
if let url = URL(string: self) {
return url
} else if let urlEscapedString = addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed),
let escapedURL = URL(string: urlEscapedString) {
return escapedURL
}
return nil
}
}
Use the apiKey after encoding it:
let encodedKey = key.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)

Issue assigning variable value to open Apple Maps when pressing a UILabel

Essentially I am parsing JSON data and assigning it to a variable called addressPressNow I then have the following function that executes when a user taps on a UILabel:
The goal is to have Apple Maps open provided the variable value it contains.
Because I am assigning an address to a variable it will contain spaces
ex: 3981 Test Drive Cupertino CA 95014
NOTE: The value of the variable is being passed correctly because when I do print(addressPressNow) in func tapFunction it prints correctly.
#objc
func tapFunction(sender:UITapGestureRecognizer) {
let targetURL = NSURL(string: "http://maps.apple.com/?q=" + addressPressNow)!
UIApplication.shared.openURL(targetURL as URL)
}
The issue is I am having trouble applying the variable to the string URL with the following error:
Thread 1: Fatal error: Unexpectedly found nil while unwrapping an
Optional value
The following is how I am assigning the value to the variable:
struct FacilityInfo: Decodable {
let address: String
class infoViewController: UIViewController {
var addressPressNow : String = ""
override func viewDidLoad() {
super.viewDidLoad()
let tap = UITapGestureRecognizer(target: self, action: #selector(infoViewController.tapFunction))
addressInfo.isUserInteractionEnabled = true
addressInfo.addGestureRecognizer(tap)
let url = URL(string: "https://test/test/example”)!
let task = URLSession.shared.dataTask(with: url) { data, response, error in
// ensure there is no error for this HTTP response
guard error == nil else {
print ("error: \(error!)")
return
}
// ensure there is data returned from this HTTP response
guard let data = data else {
print("No data")
return
}
// Parse JSON into array of Car struct using JSONDecoder
guard let cars = try? JSONDecoder().decode([FacilityInfo].self, from: data), let secondCar = cars.first
else {
print("Error: Couldn't decode data into cars array")
return
}
DispatchQueue.main.async {
self.addressPressNow = secondCar.facility_address
}
}
"I am assigning an address to a variable it will contain spaces"
If the address contains spaces then creating NSURL with the string will crash. You can use addingPercentEncoding to solve the problem
if let encodedAddress = addressPressNow.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) {
let targetURL = NSURL(string: "http://maps.apple.com/?q=" + encodedAddress)!
UIApplication.shared.openURL(targetURL as URL)
}
And don't use NSURL and force unwrapping. Update it like this
if let encodedAddress = addressPressNow.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed),
let targetURL = URL(string: "http://maps.apple.com/?q=" + encodedAddress) {
UIApplication.shared.openURL(targetURL)
}
As suggested by matt use URLComponents
let addressPressNow = "3981 Test Drive Cupertino CA 95014"
var components = URLComponents(string: "http://maps.apple.com")
components?.queryItems = [URLQueryItem(name: "q", value: addressPressNow)]
print(components?.url)//http://maps.apple.com?q=3981%20Test%20Drive%20Cupertino%20CA%2095014
if let targetURL = components?.url {
UIApplication.shared.open(targetURL, options: [:], completionHandler: nil)
}
You are saying
NSURL(string: "http://maps.apple.com/?q=" + addressPressNow)!
Notice the exclamation mark at the end. That means "if there's a problem, crash me". You can hardly complain if you do in fact crash; that is what you asked to do.
Basically, never use NSURL(string:) if you can avoid it. To form a valid URL, build it up using URLComponents. And form it out of valid components. (It is impossible to say whether facility_address is a valid URL query, because you have not shown what it is.)
Example:
var comp = URLComponents()
comp.scheme = "https"
comp.host = "maps.apple.com"
comp.queryItems = [URLQueryItem(name: "q", value: "1 Infinite Loop, Cupertino, CA")]
if let url = comp.url {
print(url) // https://maps.apple.com?q=1%20Infinite%20Loop,%20Cupertino,%20CA
}
That gives us a valid URL that actually works.

Convert string to NSURL with character '\\' inside

I coding with Swift 2.0.
I got my URL string like this:
let urlString = "http://example.com/api/getfile/?filepath=C:\\1.txt"
When I convert it into NSURL, it returns nil.
let OrginUrl = NSURL(string: urlString)
Anyone know how to do this?
There are two issues that need to be addressed:
In order to include a \ it must be escaped because it is itself the escape character.
'\' characters are not allowed in URLs so they need to be URL encoded
let urlString = "http://example.com/api/getfile/?filepath=C:\\\\1.txt"
print("urlString: \(urlString)")
var escapedString = urlString.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLHostAllowedCharacterSet())
print("escapedString!: \(escapedString!)")
let orginUrl = NSURL(string: escapedString!)
print("orginUrl: \(orginUrl!)")
urlString:
http://example.com/api/getfile/?filepath=C:\\1.txt
escapedString!:
http%3A%2F%2Fexample.com%2Fapi%2Fgetfile%2F%3Ffilepath=C%3A%5C%5C1.txt
orginUrl:
http%3A%2F%2Fexample.com%2Fapi%2Fgetfile%2F%3Ffilepath=C%3A%5C%5C1.txt
You should use unicode instead of backslash twice.
stringByAddingPercentEscapesUsingEncoding is deprecated: Use stringByAddingPercentEncodingWithAllowedCharacters(_:) instead, which always uses the recommended UTF-8 encoding, and which encodes for a specific URL components or subcomponents since each URL component or subcomponent has different rules for what characters are valid.
Documentation Reference
Below is the example code:
let myLink = "http://example.com/api/getfile/?filepath=C:\u{005C}\u{005C}1.txt"
var newLink = ""
if let queryIndex = myLink.characters.indexOf("?"){
newLink += myLink.substringToIndex(queryIndex.successor())
if let filePathIndex = myLink.characters.indexOf("=")?.successor() {
newLink += myLink.substringWithRange(queryIndex.successor()...filePathIndex.predecessor())
let filePath = myLink.substringFromIndex(filePathIndex)
if let pathEscaped = filePath.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLPathAllowedCharacterSet()) {
newLink += pathEscaped
}
}
}
if let newURL = NSURL(string: newLink) {
print(newURL, separator: "", terminator: "")
} else {
print("invalid")
}
And result you will get:
"http://example.com/api/getfile/?filepath=C%3A%5C%5C1.txt"

Resources