Substrings in Swift 4 - substring

I have a url string (fake)
http://fools.sayers.mine.cs/api/analytics/ces_ssn
And I'd like to create a new URL string
http://fools.sayers.mine.cs/api/
The issue I'm facing is the last parameter ces_ssn can sometimes be anything like ces_fw or adv_let so I can't entirely set an endIndex in my code.
Is there a way to create a function that is dynamic in saying just give me the first 32 characters every time, not matter the endIndex

You have a URL. Use URL. Assuming you know you want to drop the last two parts of the path, you can do:
let myURL = URL(string: "http://fools.sayers.mine.cs/api/analytics/ces_ssn")!
let shortURL = myURL.deletingLastPathComponent().deletingLastPathComponent()
print(shortURL)
Output:
http://fools.sayers.mine.cs/api/

This works for Swift 4
var url = "http://fools.sayers.mine.cs/api/analytics/ces_ssn"
let newStr = url.prefix(32)
or probably the perfect way
if let index = url.range(of: "api/")?.upperBound {
let api = url.prefix(upTo: index)
}

You can use prefix()
"http://fools.sayers.mine.cs/api/analytics/ces_ssn".prefix(32)

That sounds prone to mistake if all of a sudden your URL is https instead of http, for example.
Instead I would do:
let url = URL(string: "http://fools.sayers.mine.cs/api/analytics/ces_ssn")!
let components = url.pathComponents
let scheme = url.scheme!
let host = url.host!
let slash = components.removeFirst()
print (components) // ["api", "analytics", "ces_ssn"]
The components you are interested in are then the 0 and 1 component, and you could reconstruct your URL like this:
let newURL = "\(scheme)://\(host)/\(components[0])/\(components[1])"
print (newURL) // "http://fools.sayers.mine.cs/api/analytics"

Related

Using String addingPercentEncoding to encode a string as query param

I have an input string "+20" and I am trying to pass that as query parameter in url.
So I am trying to encode the myInputString by doing
let s1 = myInputString.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)
But in the debugger, the string s1 still shows as '+20' instead of '%2B20'
Is there something I did wrong?
As already mentioned by matt + is a legal URL character. If you really need to encode it you would need to create your own custom urlQueryAllowed and subtract the plus sign from it:
extension CharacterSet {
static let allowedCharacters = urlQueryAllowed.subtracting(.init(charactersIn: "+"))
}
let myInputString = "+20"
let s1 = myInputString.addingPercentEncoding(withAllowedCharacters: .allowedCharacters) // "%2B20"

URL Builder in Swift

I am newbie on Swift and I am building API URL string as follows before pass it to URLSession.
I wonder there is a better way of doing it?
let jsonUrlString = Constants.API_URL + "/" + Constants.PATH + "/"
+ String(page)+"/" + Constants.NUMBER_ITEMS_PER_PAGE
The proper way to build a URL is to use URL and URLComponents. Simply appending strings together is error prone and it does not properly escape special characters that you might have in your values.
Here's one possible solution using URL:
if let baseURL = URL(string: Constants.API_URL) {
let jsonURL = baseURL.appendingPathComponent(Constants.PATH)
.appendingPathComponent(String(page))
.appendingPathComponent(Constants.NUMBER_ITEMS_PER_PAGE)
// use jsonURL with your URLSession
}
Another option with URLComponents (this properly ensures special characters are encoded):
if let baseComps = URLComponents(string: Constants.API_URL) {
var components = baseComps
components.path = "/\(Constants.PATH)/\(page)/\(Constants.NUMBER_ITEMS_PER_PAGE)"
if let jsonURL = components.url {
// use jsonURL with your URLSession
}
}
Also there is another way in swift to build string is called interpolation and mostly this one is used by developers.
If you using this you don't have to take your Int or another type value into string, Because is automatic take value into string.
i.e
let myValue = 3
let intToString = "\(myValue)" // "3"
let doubleValue = 4.5
let doubleToString = "\(doubleValue)" // "4.5"
So you URL will be like as below
let jsonUrlString = "\(Constants.API_URL)/\(Constants.PATH)/\(page)/\(Constants.NUMBER_ITEMS_PER_PAGE)"

lastPathComponent doesn't match deletingLastPathComponent

I have a requirement to add thumb_ to the the filename of a url
let url = "https://firebasestorage.googleapis.com/v0/b/xxx-xx.xxxx.com/o/images%2Fjondoe%2F-ASDFASDFSDFAS.png?alt=media&token=xxxxx"
to convert this into
let url = "https://firebasestorage.googleapis.com/v0/b/xxx-xx.xxxx.com/o/images%2Fjondoe%2Fthumb_-ASDFASDFSDFAS.png?alt=media&token=xxxxx"
I got this working using
let webURL = NSURL(string: url)
var pathArray : [String] = webURL!.pathComponents!
let thumbFile = pathArray[pathArray.count - 3] + "/" + pathArray[pathArray.count - 2]+"/thumb_\(webURL!.lastPathComponent!)"
print( webURL!.deletingLastPathComponent)
let thumbURL = webURL!.deletingLastPathComponent?.appendingPathComponent(thumbFile)
print(thumbURL!)
What I see is the lastPathComponent gives just filename
vs deletingLastPathComponent removes images%2Fjondoe%2Fthumb_-ASDFASDFSDFAS.png , I'm thinking there should be some more easy /break proof way of doing this, there are suggestions to use cloud functions
etc looking forward to hear from you all
The problem occurs because the last two path separators are percent escaped (%2F) which confuses deletingLastPathComponent()
I recommend to call removingPercentEncoding before creating the URL to replace %2F with /
let urlString = "https://firebasestorage.googleapis.com/v0/b/xxx-xx.xxxx.com/o/images%2Fjondoe%2F-ASDFASDFSDFAS.png?alt=media&token=xxxxx"
let clearedURLString = urlString.removingPercentEncoding!
let url = URL(string: clearedURLString)!
let thumbLastPathComponent = "thumb_" + url.lastPathComponent
let newURL = url.deletingLastPathComponent().appendingPathComponent(thumbLastPathComponent)
print(newURL)
Normally the best solution would be to use URLComponents. But the percent escaped forward slash (%2F) is not recognized as a valid path separator.
The code is Swift 3, it's highly recommended to update, Swift 4 is raring to go.

Remove suffix from filename in Swift

When trying to remove the suffix from a filename, I'm only left with the suffix, which is exactly not what I want.
What (how many things) am I doing wrong here:
let myTextureAtlas = SKTextureAtlas(named: "demoArt")
let filename = (myTextureAtlas.textureNames.first?.characters.split{$0 == "."}.map(String.init)[1].replacingOccurrences(of: "\'", with: ""))! as String
print(filename)
This prints png which is the most dull part of the whole thing.
If by suffix you mean path extension, there is a method for this:
let filename = "demoArt.png"
let name = (filename as NSString).deletingPathExtension
// name - "demoArt"
Some people here seem to overlook that a filename can have multiple periods in the name and in that case only the last period separates the file extension. So this.is.a.valid.image.filename.jpg and stripping the extension should return this.is.a.valid.image.filename and not this (as two answers here would produce) or anything else in between. The regex answer works correctly but using a regex for that is a bit overkill (probably 10 times slower than using simple string processing). Here's a generic function that works for everyone:
func stripFileExtension ( _ filename: String ) -> String {
var components = filename.components(separatedBy: ".")
guard components.count > 1 else { return filename }
components.removeLast()
return components.joined(separator: ".")
}
print("1: \(stripFileExtension("foo"))")
print("2: \(stripFileExtension("foo.bar"))")
print("3: \(stripFileExtension("foo.bar.foobar"))")
Output:
foo
foo
foo.bar
You can also split the String using componentsSeparatedBy, like this:
let fileName = "demoArt.png"
var components = fileName.components(separatedBy: ".")
if components.count > 1 { // If there is a file extension
components.removeLast()
return components.joined(separator: ".")
} else {
return fileName
}
To clarify:
fileName.components(separatedBy: ".")
will return an array made up of "demoArt" and "png".
In iOS Array start with 0 and you want name of the file without extension, so you have split the string using ., now the name will store in first object and extension in the second one.
Simple Example
let fileName = "demoArt.png"
let name = fileName.characters.split(".").map(String.init).first
If you don't care what the extension is. This is a simple way.
let ss = filename.prefix(upTo: fileName.lastIndex { $0 == "." } ?? fileName.endIndex))
You may want to convert resulting substring to String after this. With String(ss)
#Confused with Swift 4 you can do this:
let fileName = "demoArt.png"
// or on your specific case:
// let fileName = myTextureAtlas.textureNames.first
let name = String(fileName.split(separator: ".").first!)
print(name)
Additionally you should also unwrapp first but I didn't want to complicate the sample code to solve your problem.
Btw, since I've also needed this recently, if you want to remove a specific suffix you know in advance, you can do something like this:
let fileName = "demoArt.png"
let fileNameExtension = ".png"
if fileName.hasSuffix(fileNameExtension) {
let name = fileName.prefix(fileName.count - fileNameExtension.count)
print(name)
}
How about using .dropLast(k) where k is the number of characters you drop from the suffix ?
Otherwise for removing extensions from path properly from filename, I insist you to use URL and .deletingPathExtension().lastPathComponent.
Maybe a bit overhead but at least it's a rock solid Apple API.
You can also use a Regexp to extract all the part before the last dot like that :
let fileName = "test.png"
let pattern = "^(.*)(\\.[a-zA-Z]+)$"
let regexp = try! NSRegularExpression(pattern: pattern, options: [])
let extractedName = regexp.stringByReplacingMatches(in: fileName, options: [], range: NSMakeRange(0, fileName.characters.count), withTemplate: "$1")
print(extractedName) //test
let mp3Files = ["alarm.mp3", "bubbles.mp3", "fanfare.mp3"]
let ringtonsArray = mp3Files.flatMap { $0.components(separatedBy: ".").first }
You can return a new string removing a definite number of characters from the end.
let fileName = "demoArt.png"
fileName.dropLast(4)
This code returns "demoArt"
One liner:
let stringWithSuffixDropped = fileName.split(separator: ".").dropLast().joined(separator: ".")

How to get the video ID of a YouTube string

(XCode 6.3.2, Swift 1.2)
After researching the internet for the whole evening I already know that this can't be done that easily.
I simply want to get the video ID of a YouTube link. So the "ABCDE" in "https://www.youtube.com/watch?v=ABCDE"
What I got so far:
var url = "https://www.youtube.com/watch?v=ABCDE"
let characterToFind: Character = "="
let characterIndex = find(url, characterToFind)
println(characterIndex)
url.substringFromIndex(advance(url.startIndex, characterIndex))
Prinln outputs: Optional(31)
That's right but I can't use this because it's no index.
XCode also states for the last line: Missing argument for parameter #3 in call
I also don't know what the 3rd parameter of substringFromIndex should be.
Many thanks in advance!
In your case there is no need to create an NSURL as other answers do. Just use:
var url = "https://www.youtube.com/watch?v=ABCDE"
if let videoID = url.componentsSeparatedByString("=").last {
print(videoID)
}
Swift 3+ version:
var url = "https://www.youtube.com/watch?v=ABCDE"
if let videoID = url.components(separatedBy: "=").last {
print(videoID)
}
You can use NSURL query property as follow:
Xcode 8.3.1 • Swift 3.1
let link = "https://www.youtube.com/watch?v=ABCDE"
if let videoID = URL(string: link)?.query?.components(separatedBy: "=").last {
print(videoID) // "ABCDE"
}
Another option is to use URLComponents:
let link = "https://www.youtube.com/watch?v=ABCDE"
if let videoID = URLComponents(string: link)?.queryItems?.filter({$0.name == "v"}).first?.value {
print(videoID) // "ABCDE"
}
Swift 1.x
let link = "https://www.youtube.com/watch?v=ABCDE"
if let videoID = NSURL(string: link)?.query?.componentsSeparatedByString("=").last {
println(videoID) // "ABCDE"
}
You need to unwrap the optional to use the index:
var url = "https://www.youtube.com/watch?v=ABCDE"
let characterToFind: Character = "="
if let index = find(url, characterToFind) { // Unwrap the optional
url.substringFromIndex(advance(index, 1)) // => "ABCDE"
}
find returns an optional – because the character might not be found, in which case it will be nil. You need to unwrap it to check it isn’t and to get the actual value out.
You can also use a range-based subscript on the index directly, rather than using advance to turn it into an integer index:
let url = "https://www.youtube.com/watch?v=ABCDE"
if let let characterIndex = find(url, "=") {
let value = url[characterIndex.successor()..<url.endIndex] // ABCDE
}
else {
// error handling, if you want it, here
}
You have more options if there is a reasonable default in the case of “not found” For example, if you just want an empty string in that case, this will work:
let idx = find(url, "=")?.successor() ?? url.endIndex
let value = url[idx..<url.endIndex]
Or, maybe you don’t even need to deal with the optionality right now, so you’re happy to leave the result optional as well:
// value will be Optional("ABCD")
let value = find(url, "=").map { url[$0.successor()..<url.endIndex] }
For a rundown on optionals, take a look here and here.
Also, rather than hand-parsing URL strings at all, you might find the info here useful.
With your url format, you can get the 'v' parameter's value by converting to NSURL then get parameters and separate it by '='.
var url: NSURL = NSURL(string: "https://www.youtube.com/watch?v=RAzOOfVA2_8")!
url.query?.componentsSeparatedByString("=").last
url.query returns v=RAzOOfVA2_8
If the link has more than 1 parameter, you can get all parameters then do a loop to verify each parameter:
var url: NSURL = NSURL(string: "https://www.youtube.com/watch?v=RAzOOfVA2_8")!
var params = url.query?.componentsSeparatedByString("&")
if let _params = params { //have parameters in URL
for param in _params { //verify each pair of key & value in your parameters
var _paramArray = param.componentsSeparatedByString("=") //separate by '=' to get key & value
if (_paramArray.first?.compare("v", options: nil, range: nil, locale: nil) == NSComparisonResult.OrderedSame) {
println(_paramArray.last)
}
}
} else {
//url does not have any parameter
}