Swift: define a variable then conditionally set it? - swift

This is probably a very simple question...
Here I have some Swift code:
var url: NSURL
if (someValue > 1) {
var fullUrl = self.baseUrl + "?id=" + (someValue as! String);
var url = NSURL(string: fullUrl);
}
else {
var url = NSURL(string: self.baseUrl);
}
let req = NSURLRequest(URL: url);
webView.loadRequest(req);
As you can see, I need to conditionally initialize the url variable. However, the method I've used in just about every other strong-type language - initialize the variable then assign to it in the if statement - doesn't seem to be the Swiftly way of doing it.
The above code will refuse to compile with the error "variable 'url' used for being initialized" error. (Of course, from this program flow, you can see that url would always be initialized, but Swift apparently takes nothing for granted.)
Not defining the variable outside the if block results in an unknown identifier error - it seems variables defined inside an if block are scoped only within that block.
Short of just defining an empty NSURL object, is there a better way to do this?
Preferably I'd actually like to use let on this because the NSURL object will never change once it's been created - and Swift tends to recommend using let wherever possible. However, how would you use let in this scenario? (I tried it - the same types of errors occur. Swift is unhappy that I don't actually declare the object itself on the same line as the let statement, but using let inside the if block results in a scope issue.)

The problem is you're redefining url as var url = NSURL(string: self.baseUrl); within the context of the if-else so the url outside is never actually set.
This will fix the error and allow you to use let for url.
let url: NSURL
if (someValue > 1) {
var fullUrl = self.baseUrl + "?id=" + (someValue as! String);
url = NSURL(string: fullUrl);
} else {
url = NSURL(string: self.baseUrl);
}
let req = NSURLRequest(URL: url);
webView.loadRequest(req);

You are redeclaring your property each time you try to set it. Try:
url = /*stuff here*/;
inside your if statements instead of:
var url = /*stuff here*/;

You have to define outside of the {}, that sets it's scope. In your code you're redefining url in the if and the else, you need to remove the re-declaration of "var" so that you can assign your url to the url out of the scope of the if / else statement.
You can use another if statement to check for nil and unwrap it if it's not nil, like
if url != nil {
let req = NSURLRequest(URL: url!)
}

You variable url need to be conditional do something like this.
var url: NSURL?
if (someValue > 1) {
var fullUrl = self.baseUrl + "?id=" + (someValue as! String);
url = NSURL(string: fullUrl);
}
else {
url = NSURL(string: self.baseUrl);
}
let req = NSURLRequest(URL: url);
webView.loadRequest(req);

Related

Swift Quotation Mark in URL

I'm trying to do a request with Alamofire to this url:
https://overpass-api.de/api/interpreter?data=[out:json];(way[highway~"^(motorway)$"][!maxspeed](around:5,45.792790,3.062686););out%20tags;
but it contains a double quotation mark and the cast into URL fails.
I've escaped the " with the backslash
let urlString = "https://overpass-api.de/api/interpreter?data=[out:json];(way[highway~\"^(motorway)$\"][!maxspeed](around:5,45.792790,3.062686););out%20tags;"
but when I convert the string in a URL, it returns nil
let url = URL(string: urlString)
I've tried replacing the " symbols with %22, but it is still not working. I tried using the addingPercent but it results in a wrong url and returns error 400 or 404
let urlWithEconding = urlString.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)
I also tried to use AlamoFire method for url casting and encoding, but I cannot make it work...
Here is how you can use URLComponents
let queryValue = "[out:json];(way[highway~\"^(motorway)$\"][!maxspeed](around:5,45.792790,3.062686););out tags;"
var components = URLComponents()
components.scheme = "https"
components.host = "overpass-api.de"
components.path = "/api/interpreter"
components.queryItems = [URLQueryItem(name: "data", value: queryValue)]
If you don't want to escape the quotes you can define the variable like this
let queryValue = """
[out:json];(way[highway~"^(motorway)$"][!maxspeed](around:5,45.792790,3.062686););out tags;
"""

SWIFT URL String build

How do I build the URL as below:
/sap/opu/odata/SAP/ZTM_FOR_MOBILITY_SRV/BusinessPartnerSet('BP-CR-EM01')/?$format=json
I want specifically the part - BusinessPartnerSet('BP-CR-EM01').
BP-CR-EM01 value is dynamic value.
let newUrl = url + "'\(businessPartners[myIndex].partnerId)'"
url has the fixed URL and the + operator followed by dynamic value. Please help me with your suggestions.
I recommend to use URLComponents
var components = URLComponents(string: "http://mycompany.com")!
components.path = "/sap/opu/odata/SAP/ZTM_FOR_MOBILITY_SRV/BusinessPartnerSet('\(businessPartners[myIndex].partnerId)')/"
components.queryItems = [URLQueryItem(name: "$format", value:"json")]
if let url = components.url {
print(url)
// or if you need the string
print(url.absoluteString)
}

Reading URLS in JSON api with swift

For work we have a third party company which supply a JSON api for some functionality. The JSON contains urls which I try to map in my code with URL(string: ...) but this fails on some urls which have spaces.
For example:
var str = "https://google.com/article/test test.html"
let url = URL(string: str) //nil
Should I ask the third party to encode their URLs ?
Is this normal or should I try to add encoding myself?
Encoding myself is hard I think because the path should be encoded different from the query and the host shouldn't be encoded etc.
Or am I overthinking this?
If the URL contains spaces in its path, escape the characters with addingPercentEncoding(withAllowedCharacters passing the urlPathAllowed character set:
let str = "https://google.com/article/test test.html"
if let escapedString = str.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlPathAllowed),
let url = URL(string:escapedString) {
print(url)
} else {
print("url \(str) could not be encoded")
}
What I would do if I were you, is to split the string up on the space, try converting each of the elements to a url, and when that works save it in your variable.
var str = "https://google.com/article/test test.html"
var url: URL? = nil
for urlString in str.components(separatedBy: .whitespacesAndNewlines) {
let url = URL(string: urlString)
if url != nil {
break
}
}
// url might be nil here, so test for value before using it
If each URL that you get from the API is in the format in your example, you can instead just grab the first element after spitting the string.
var str = "https://google.com/article/test test.html"
if let urlString = str.components(separatedBy: .whitespacesAndNewlines).first {
let url = URL(string: urlString)
}
// url might be nil here, so test for value before using it

AppleWatch Messages URL works hard coded but not with variables

TLDR When I hard code phone numbers into a URL it opens in watch messages correctly, but when I use a variable string with the numbers typed in exactly the same way inside of it, it doesn't.
Example:
NSURL(string: "sms:/open?addresses=8888888888,9999999999,3333333333&body=Test")
Above code works but below code doesn't:
let hardCode = "8888888888,9999999999,3333333333"
NSURL(string: "sms:/open?addresses=\(hardCode)&body=Test")
FULL DETAILS:
I am making a URL from variables to open messages on the Apple Watch with pre-filled contents. I am getting the phone numbers from the contact book and storing them in an array. They are provided in this format:
(###) ###-#### but need to be ##########
I tested the code by hard-coding phone numbers into the URL and it works properly with all contacts and completed body:
if let urlSafeBody = urlSafeBody, url = NSURL(string: "sms:/open?addresses=8888888888,9999999999,3333333333&body=\(urlSafeBody)") {
print("FINAL URL: \(url)")
WKExtension.sharedExtension().openSystemURL(url)
}
But when I build the phone number values programmatically it does not work:
//holds phone numbers without special chars
var tempArray: [String] = []
//if I can access the unformatted numbers
if let recips = saveData["recips"] as? [String] {
//for each number provided
recips.forEach { (person: String) in
//remove all non-numerical digits
//person is now (###) ###-####
let newPerson = person.digitsOnly()
//newPerson is ##########
print(person)
print("->\(newPerson)")
//add formatted number to tempArray
tempArray.append(newPerson)
}
}
//combine all numbers with "," between as a string
let recipString = tempArray.joinWithSeparator(",")
//recipString contains ##########,##########,##########...
extension String {
func digitsOnly() -> String{
let stringArray = self.componentsSeparatedByCharactersInSet(
NSCharacterSet.decimalDigitCharacterSet().invertedSet)
let newString = stringArray.joinWithSeparator("")
return newString
}
}
I then add the "recipString" variable to the NSURL in the below code:
let messageBody = "test"
let urlSafeBody = messageBody.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLHostAllowedCharacterSet())
if let urlSafeBody = urlSafeBody, url = NSURL(string: "sms:/open?addresses=\(recipString)&body=\(urlSafeBody)") {
print("FINAL URL: \(url)")
WKExtension.sharedExtension().openSystemURL(url)
}
The FINAL URL print shows the correct string, but the messages app does not open properly, and shows quick reply menu instead of composed message window. It matches the functioning hard coded number version exactly, but behaves differently.
Totally lost, hope someone can help!
UPDATE 1
Here are the debug prints for both versions of the URL:
Manually declared (not created from recipString but actually declared in the URL string explicitly):
This version works
FINAL URL: sms:/open?addresses=0000000000,1111111111,2222222222,3333333333,4444444444&body=test
Variable created (using recipString):
This version doesn't
FINAL URL: sms:/open?addresses=0000000000,1111111111,2222222222,3333333333,4444444444&body=test
I have also tried applying url encoding to the "recipString" variable by using the below if let:
if let urlSafeRecip = recipString.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLQueryAllowedCharacterSet()) {
if let urlSafeBody = urlSafeBody, url = NSURL(string: "sms:/open?addresses=\(urlSafeRecip)&body=\(urlSafeBody)") {
print("FINAL URL: \(url)")
WKExtension.sharedExtension().openSystemURL(url)
}
}
UPDATE 2
I tested to see if the hardcode version of numbers matches the recipString exactly via this code:
let hardCode = "0000000000,1111111111,2222222222,3333333333,4444444444"
let isEqual = (hardCode == recipString)
if isEqual {
print("hardCode matches recipString")
}
else {
print("hardCode does not match recipString")
}
Debug prints:
hardCode matches recipString
UPDATE 3
I have confirmed that:
When a URL is made with hard coded numbers vs. numbers that I make from variables, checking == between them returns true.
In every test I can do between the two version of the url, it matches.
NOTES AFTER CORRECT ANSWER FOUND:
This type of URL formatting will ONLY work with multiple addresses in the URL. If you do not have multiple addresses you will need to do the following, which is undocumented but none-the-less works. I found this by bashing my face on the keyboard for hours, so if it helps you an upvote is deserved :)
follow the answer marked below, and then use this type of logic check before making the URL in the doItButton() function he mentioned:
func setupAndSendMsg(saveData: NSDictionary) {
if let urlSafeBody = createBody(saveData) {
let theNumbers = createNumbers(saveData).componentsSeparatedByString(",")
print(theNumbers.count-1)
if theNumbers.count-1 > 0 {
if let url = NSURL(string: "sms:/open?addresses=\(createNumbers(saveData))&body=\(urlSafeBody)") {
print(url)
WKExtension.sharedExtension().openSystemURL(url)
}
} else {
if let url = NSURL(string: "sms:/open?address=\(createNumbers(saveData)),&body=\(urlSafeBody)") {
print(url)
WKExtension.sharedExtension().openSystemURL(url)
}
}
}
}
My guess is that it is not the acctual openSystemUrl call that is the problem. I believe there must be something with the code that is building the number string programmatically.
The code bellow is a simplified version of all the code you have posted. I have confirmed that it is working on my Apple Watch. It opens the Messages app with pre-populated numbers & body text.
Take one more look at your code and see if there is something your missing. If you can't find anything, just delete the code and re-write it, probably will be faster then spotting the weird issue.
Once again the code bellow is confirmed working as expected, so you should be able to get it to work. (or just copy & paste my code) :)
class InterfaceController: WKInterfaceController {
#IBAction func doItButton() {
if let urlSafeBody = createBody() {
if let url = NSURL(string: "sms:/open?addresses=\(createNumbers())&body=\(urlSafeBody)") {
print(url)
WKExtension.sharedExtension().openSystemURL(url)
}
}
}
private func createBody() -> String? {
let messageBody = "hello test message"
return messageBody.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLHostAllowedCharacterSet())
}
private func createNumbers() -> String {
let numbers = ["(111) 222-3333", "(444) 555-6666"]
var tempArray: [String] = []
numbers.forEach { (number: String) in
tempArray.append(number.digitsOnly())
}
return tempArray.joinWithSeparator(",")
}
}
extension String {
func digitsOnly() -> String{
let stringArray = self.componentsSeparatedByCharactersInSet(
NSCharacterSet.decimalDigitCharacterSet().invertedSet)
let newString = stringArray.joinWithSeparator("")
return newString
}
}
With above said I would recommend against using undocumented Apple features for anything you plan on putting on the App Store for the reasons already mentioned in comments.

Swift - Variable is updated as part of in an if statement, but it's then reverting afterwards

I'm building a browser that then goes off and gets some data from a variety of APIs for the URL entered. I've got the basic controls all up and running, but I'm a stumped on some code that's intended to append a 'HTTP://' to the start of the URL if the user doesn't enter it.
func loadAddressURL(URLpath: String) {
//add 'http' if not entered.
var first4 = Range(start: URLpath.startIndex,
end: advance(URLpath.startIndex, 4))
var URL = URLpath
println(URL)
if URLpath.substringWithRange(first4) != "http" {
var URL = "http://" + URLpath
println(URL)
}
println(URL)
let requestURL = NSURL(string: URL)
let request = NSURLRequest(URL: requestURL!)
webview.loadRequest(request)
}
On the first print, the URL is coming in as the user entered it. The second print statement in the If statement is displaying the value with the added 'HTTP://' as expected, however after the if statement finishes the URL Var goes back to the original string the user entered, and I'm buggered if I can figure out why. Any ideas?
The problem is with variable scopes. If you look at your code, you define a variable URL at the top level inside the function. Then you compare the prefix and append http, but then you define a new variable inside the if statement (which is scoped now). So, the URL variable outside if is different than the variable inside if, so your changes to URL is not seen outside the if statement.
Simple make the following changes, and it should be fine,
func loadAddressURL(URLpath: String) {
//add 'http' if not entered.
var first4 = Range(start: URLpath.startIndex,
end: advance(URLpath.startIndex, 4))
var URL = URLpath
println(URL)
if URLpath.substringWithRange(first4) != "http" {
URL = "http://" + URLpath
println(URL)
}
println(URL)
let requestURL = NSURL(string: URL)
let request = NSURLRequest(URL: requestURL!)
webview.loadRequest(request)
}
it is not the same var
var URL = "http://" + URLpath
creates a new var. use instead
URL = "http://" + URLpath