How can I use a # symbol instead of ? in URLQueryItem? - swift

If I create a URL like this:
guard var urlComponents = URLComponents(url: "https://example.com/something", resolvingAgainstBaseURL: false) else {
return
}
let queryItems = URLQueryItem(name: "token", value: token)
urlComponents.queryItems = [queryItems]
guard let urlWithQueryItem = urlComponents.url else {
return
}
I want the end result to be something like https://example.com/something#token=7as78f6asd678768asd768asd678
Instead of the default https://example.com/something?token=7as78f6asd678768asd768asd678
(I'm looking for something smarter than a search and replace of the ? character)
Thanks

As others have noted, URLQueryItem object is more intended for use with querystrings vs anchors/fragments. With that said, below is a function that helps you accomplish what your questions is asking.
func constructURL(withURL urlBase: String, andFragmentFromQueryItem queryItem: URLQueryItem) -> URL?{
guard let url = URL(string:urlBase) else {
return nil
}
guard var urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false) else {
return nil
}
let fragmentKey = queryItem.name
let fragmentValue = queryItem.value ?? ""
urlComponents.fragment = "\(fragmentKey)=\(fragmentValue)"
guard let urlWithFragment = urlComponents.url else {
return nil
}
return urlWithFragment
}
let url = constructURL(withURL:"https://example.com/something",
andFragmentFromQueryItem: URLQueryItem(name: "token", value: "tokenValue"))
let urlString = url?.absoluteString
print("\(urlString!)")
Here is a link to a working Swift fiddle of the code.
http://play.swiftengine.io/code/8JKI3/2

As #rmaddy points out, there is a definite difference between the meaning of ? and #. '?' introduces query parameters, which are part of the URL. '#' introduces the "fragment" which is not considered part of the URL.
You can, however use URLComponents to add a fragment to a URL. Just use the fragment property instead:
urlComponents.fragment = "token=\(token)"

Related

Text not displaying value of Var. Swift

I'm a beginner, but I'm trying to display the "valueWeWantToGrab" to a text field but is not showing the result.
this is my code to display someone's Instagram follower amount
struct ContentView: View {
#State var valueWeWantToGrab = ""
var body: some View {
let baseUrl = "http://www.instagram.com/"
let username = ""
let url = URL(string: baseUrl + username)!
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let data = data else {
print("data was nil")
return
}
guard let htmlString = String(data: data, encoding: .utf8) else {
print("couldn't cast data into String")
return
}
print(htmlString)
let leftSideString = """
edge_followed_by":{"count":
"""
let rightSideString = """
},"followed_by_viewer
"""
guard
let leftSideRange = htmlString.range(of: leftSideString)
else {
print("couldn't find left range")
return
}
guard
let rightSideRange = htmlString.range(of: rightSideString)
else {
print("couldn't find right range")
return
}
let rangeOfTheData = leftSideRange.upperBound..<rightSideRange.lowerBound
var valueWeWantToGrab = htmlString[rangeOfTheData]
print(valueWeWantToGrab) // prints the follower count: 19093
}
Text(valueWeWantToGrab)
}
}
The issue is that you declare the variable valueWeWantToGrab twice: Once at the top annotated with #State and once towards the bottom within the getter function of the body variable.
What you actually want to do at the bottom is assigning to the variable declared at the top instead of declaring a new variable by removing var from the line var valueWeWantToGrab = htmlString[rangeOfTheData]. Doing that, you also have to wrap htmlString[rangeOfTheData] with a String initializer (String()) as htmlString[rangeOfTheData] gives you a value of type Substring, not a value of type String. This is the result:
...
let rangeOfTheData = leftSideRange.upperBound..<rightSideRange.lowerBound
valueWeWantToGrab = String(htmlString[rangeOfTheData])
print(valueWeWantToGrab) // prints the follower count: 19093
...
Also, to make your code a little cleaner (though not required to make it work), it might be a good idea to move the code that loads the data to a dedicated method instead of putting it in the getter of the body variable (as a general rule of thumb, try to only put UI elements in the body of SwiftUI Views and do the business logic somewhere else - that will help you to keep your code clean and readable). You can then call the method in the initializer of your ContentView. This comes with the benefit that you can then reuse that method elsewhere - for example to implement a refresh button in the future. This is what the final result might look like:
struct ContentView: View {
#State var valueWeWantToGrab = ""
var body: some View {
Text(valueWeWantToGrab)
}
init() {
loadFollowerCount()
}
private func loadFollowerCount() {
let baseUrl = "http://www.instagram.com/"
let username = ""
let url = URL(string: baseUrl + username)!
URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let data = data else {
print("data was nil")
return
}
guard let htmlString = String(data: data, encoding: .utf8) else {
print("couldn't cast data into String")
return
}
print(htmlString)
let leftSideString = """
edge_followed_by":{"count":
"""
let rightSideString = """
},"followed_by_viewer
"""
guard
let leftSideRange = htmlString.range(of: leftSideString)
else {
print("couldn't find left range")
return
}
guard
let rightSideRange = htmlString.range(of: rightSideString)
else {
print("couldn't find right range")
return
}
let rangeOfTheData = leftSideRange.upperBound..<rightSideRange.lowerBound
valueWeWantToGrab = String(htmlString[rangeOfTheData])
print(valueWeWantToGrab) // prints the follower count: 19093
}
}
}
One final tip in case you come from a web development background (which is what I am guessing): vars in Swift are different from vars in JavaScript. In JavaScript, the redeclaration would have overridden the previous value of valueWeWantToGrab and your code would have worked. However, in Swift, this actually declares a new, different variable in the local scope of the getter function. Have a look at the Swift docs to find out more about how variable declarations work in Swift.

How do I return safely unwrapped optionals which are outside of their scope?

I'm a noob, bear with me:
func createEmployeeCode() -> String? {
let email = UserDefaults.standard.object(forKey: "email_Saved") as? String
let employeeCode = UserDefaults.standard.object(forKey: "employeeCode_Saved") as? String
if let emailString = email,
let employeeCodeString = employeeCode {
return (emailString+employeeCodeString)
}
return (emailString+employeeCodeString) //ERROR: Use of unresolved identifier 'employeeCodeString' & Use of unresolved identifier 'emailString'
}
I understand the reason the error shows is because I'm trying to return something that is in a different scope here, but how else can I get the function to return the 2 strings together without the "Optional[...]" tag?
Here's how I'd expect it to be done in a normal production app
(You wouldn't do any of this in a normal production app! But this is the "idiom" you're looking for.)
func createCodeIfPossible() -> String? {
guard let e = UserDefaults.standard.object(forKey: "email_Saved") else {
print("serious problem, there's no email saved")
// at this point the app is completely buggered, so give up
return ""
}
guard let c = UserDefaults.standard.object(forKey: "employeeCode_Saved") else {
print("serious problem, there's no code saved")
// at this point the app is completely buggered, so give up
return ""
}
return e + c
}
Do note that the return is largely meaningless - in the app in question, if one of the guards breaks you are "totally screwed". I'd probably just return a blank string (or more likely something like "name_error") since at this point the app architecture is hopelessly broken.
(Sidenote: use UserDefaults.standard.string(forKey:).)
The issue is that you can't know if those strings DO both exist or not--if they do, you already have a great if let that returns your answer. The question now is what do you want to do if one or both are nil? Maybe you'd like to return nil from the entire function. If so,
func createEmployeeCode() -> String? {
let email = UserDefaults.standard.string(forKey: "email_Saved")
let employeeCode = UserDefaults.standard.string(forKey: "employeeCode_Saved")
if let emailString = email,
let employeeCodeString = employeeCode {
return (emailString+employeeCodeString) //successful unwrapping, let's concatenate!
}
return nil //if one or both `if let`s fail, we end up here
}
Of course, you could do whatever you'd like in that "bad" case. Maybe you'd like to show whatever string you DO have. In that case:
func createEmployeeCode() -> String {
let email = UserDefaults.standard.string(forKey: "email_Saved")
let employeeCode = UserDefaults.standard.string(forKey: "employeeCode_Saved")
return (email ?? "") + (employeeCode ?? "") //this works in the "good" case, too, and uses the nil coalescing operator `??`
}
In this case, you can see that the return value is no longer optional. This is because even if neither string exists, it'll concatenate two empty strings. If this feels icky, you could keep your optional return value and do a quick check before returning:
if email == nil && employeeCode == nil { return nil }
func createEmployeeCode() -> String {
var finalString = String()
if let email = UserDefaults.standard.string(forKey: "email_Saved"), let employeeCode = UserDefaults.standard.string(forKey: "employeeCode_Saved") {
finalString = email + employeeCode
}
return finalString
}
When assign back the values from userDefaults you've been trying to get as an object instead of string
func createEmployeeCode() -> String? {
let email:String = UserDefaults.standard.string(forKey: "email_Saved") ?? ""
let employeeCode:String = UserDefaults.standard.string(forKey: "employeeCode_Saved") ?? ""
let emailString = "\(email)\(employeeCode)"
return emialString
}
There are different ways to solve this depending on what you're trying to achieve.
If you always want to create an employeeCode (even if the code will be empty):
Try using a "nil coalescing operator".
func createEmployeeCode() -> String {
let email = UserDefaults.standard.object(forKey: "email_Saved") as? String ?? ""
let employeeCode = UserDefaults.standard.object(forKey: "employeeCode_Saved") as? String ?? ""
return (email+employeeCode)
}
To explain what's happening here:
We're unwrapping email, if we don't find email then we default the value to an empty string, "".
We do the same with employeeCode.
This isn't a way I would solve every unwrap issue though, but it suits your usecase of email and employeeCode because you're always wanting to return something based on your original question. I've also changed the return type to non-optional.
If an employee code must always contain an email and and a code then we want to return nil if one of those isn't found.
Try using the guard statement. The guard statement is perfect for validation and very readable:
func createEmployeeCode() -> String? {
guard let email = UserDefaults.standard.object(forKey: "email_Saved") as? String else { return nil }
guard let employeeCode = UserDefaults.standard.object(forKey: "employeeCode_Saved") as? String else { return nil }
return (email+employeeCode)
}
Try this function:
func createEmployeeCode() -> String {
let email = UserDefaults.standard.object(forKey: "email_Saved") as? String
let employeeCode = UserDefaults.standard.object(forKey: "employeeCode_Saved") as? String
return [email, employeeCode].compactMap { $0 }.joined(separator: "")
}
It will return email or employeeCode or email+employeeCode in case one of them is nil or both are present, or empty String in case if both are missed out!

Better way to write this if condition with a force-unwrap?

Given these variables:
let urls: [URL] = []
let defaultURL = urls.first?
It's straightforward to write a conditional statement to do something based on whether there's a file or not.
if let url = defaultURL, FileManager.default.fileExists(atPath: url.path) {
// Do something
} else {
// Do something else
}
But if you don't need to do something in the first case, I can't come up with a better way to write the condition than this:
if defaultURL == nil || !FileManager.default.fileExists(atPath: defaultURL!.path) {
// Do something
}
Is there a better way to write this? I don't like the force-unwrap, but at the same time, it feels safe to write that.
There are many ways to solve this situation.
The simplest one is to extract the condition into a separate function/closure and check
func fileExists(defaultUrl: URL?) -> Bool {
guard let defaultUrl = defaultUrl else { return false }
return FileManager.default.fileExists(atPath: defaultUrl.path)
}
The same can be done using a variable:
let fileExists: Bool
if let defaultUrl = defaultUrl {
fileExists = FileManager.default.fileExists(atPath: defaultUrl.path)
} else {
fileExists = false
}
Or, you can use Optional.map:
let fileExists = defaultUrl.map { FileManager.default.fileExists(atPath: $0.path) } ?? false
You can make it more clear and readable by making a variable that makes if condition an english sentence i.e.
let fileNotExists = !(defaultUrl.map{ FileManager.default.fileExists(atPath: $0.path)} ?? false)
if fileNotExists {
//Do your stuff
}

Guard statment in Swift

I'm struggling with using a guard statement in Swift
The following is designed to strop force unwrapping
let pages = content.allpages?.pages?.compactMap{ $0.page?.html }
let titles = content.allpages?.pages?.compactMap{ $0.page?.title }
guard pages != nil && titles != nil else { let error = NSError(domain: "", code: -300, userInfo: [:]);
observer.onError(error); return }
let both = Array(zip(pages!, titles!))
It works, but I wanted to do something like
guard let pages = content.allpages?.pages?.compactMap{ $0.page?.html }, titles = content.allpages?.pages?.compactMap{ $0.page?.title } else {return}
but can't, some error about using autonomous arguments in the closure?
Why?
Trailing closure syntax isn't allowed in guard statements, because of some implementation difficulties.
Here's how I would write this:
guard let pages = content.allpages?.pages?.lazy.compactMap({ $0.page }) else {
observer.onError(NSError(domain: "", code: -300, userInfo: [:]))
return
}
let pageHTMLs = pages.compactMap { $0.html }
let pageTitles = pages.compactMap { $0.title }
let both = Array(zip(pages, titles))
Just add each closure inside a pair of brackets. (Also, add let for the titles)
guard let pages = content.allpages?.pages?.compactMap ({ $0.page?.html }), let titles = content.allpages?.pages?.compactMap ({ $0.page?.title }) else { return }

Swift String omit/convert polish accents

I have a following problem:
I am making an API request. With a city name f.e. 'Poznań" (containing some signs typical for some language), swift doesn't want to give me the result, but when I do the same request through Postman app it gives the result in a proper way. How can can I prevent swift from converting those 'strange' letters? 'city.name' is city name that I pass from the previous VC and googlePlaces API. Here is sample of a request and part of my code:
https://samples.openweathermap.org/data/2.5/weather?q=London&appid=b6907d289e10d714a6e88b30761fae22
private let kWeatherAPIURL = "https://api.openweathermap.org/data/2.5/weather?q=%#&appid=%#"
let urlString = String(format: kWeatherAPIURL, city.name, weatherAPIKey)
guard let url = URL(string: urlString) else {
print("address doesnt exist!")
return
}
I am force unwrapping here for brevity:
let kWeatherAPIURL = "https://api.openweathermap.org/data/2.5/weather?q=%#&appid=%#"
let weatherAPIKey = "YourWeatherAPIKey"
let cityName = "Poznań"
let cString = cityName.cString(using: .utf8)!
let utf8CityName = cityName.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!
let urlString = String(format: kWeatherAPIURL, utf8CityName, weatherAPIKey)
let url = URL(string: urlString)!
//https://api.openweathermap.org/data/2.5/weather?q=Pozna%C5%84&appid=YourWeatherAPIKey
A safe approach would be to use URL components:
let weatherAPIKey = "YourWeatherAPIKey"
let cityName = "Poznań"
var components = URLComponents()
components.scheme = "https"
components.host = "api.openweathermap.org"
components.path = "/data/2.5/weather"
components.queryItems = [URLQueryItem(name: "q", value: cityName),
URLQueryItem(name: "appid", value: weatherAPIKey)
]
print(components.url!) //https://api.openweathermap.org/data/2.5/weather?q=Pozna%C5%84&appid=YourWeatherAPIKey
An example of using URLComponents.
Prepare a function like this:
func createWeatherAPIURL(cityName: String, apiKey: String) -> URL? {
let kWeatherAPIURL = "https://api.openweathermap.org/data/2.5/weather"
var urlCompo = URLComponents(string: kWeatherAPIURL)
urlCompo?.queryItems = [
URLQueryItem(name: "q", value: cityName),
URLQueryItem(name: "appid", value: apiKey)
]
return urlCompo?.url
}
And use it:
guard let url = createWeatherAPIURL(cityName: city.name, apiKey: weatherAPIKey) else {
print("address doesnt exist!")
return
}