How to split filename from file extension in Swift? - ios

Given the name of a file in the bundle, I want load the file into my Swift app. So I need to use this method:
let soundURL = NSBundle.mainBundle().URLForResource(fname, withExtension: ext)
For whatever reason, the method needs the filename separated from the file extension. Fine, it's easy enough to separate the two in most languages. But so far I'm not finding it to be so in Swift.
So here is what I have:
var rt: String.Index = fileName.rangeOfString(".", options:NSStringCompareOptions.BackwardsSearch)
var fname: String = fileName .substringToIndex(rt)
var ext = fileName.substringFromIndex(rt)
If I don't include the typing on the first line, I get errors on the two subsequent lines. With it, I'm getting an error on the first line:
Cannot convert the expression's type '(UnicodeScalarLiteralConvertible, options: NSStringCompareOptions)' to type 'UnicodeScalarLiteralConvertible'
How can I split the filename from the extension? Is there some elegant way to do this?
I was all excited about Swift because it seemed like a much more elegant language than Objective C. But now I'm finding that it has its own cumbersomeness.
Second attempt: I decided to make my own string-search method:
func rfind(haystack: String, needle: Character) -> Int {
var a = Array(haystack)
for var i = a.count - 1; i >= 0; i-- {
println(a[i])
if a[i] == needle {
println(i)
return i;
}
}
return -1
}
But now I get an error on the line var rt: String.Index = rfind(fileName, needle: "."):
'Int' is not convertible to 'String.Index'
Without the cast, I get an error on the two subsequent lines.
Can anyone help me to split this filename and extension?

Swift 5.0 update:
As pointed out in the comment, you can use this.
let filename: NSString = "bottom_bar.png"
let pathExtention = filename.pathExtension
let pathPrefix = filename.deletingPathExtension

This is with Swift 2, Xcode 7: If you have the filename with the extension already on it, then you can pass the full filename in as the first parameter and a blank string as the second parameter:
let soundURL = NSBundle.mainBundle()
.URLForResource("soundfile.ext", withExtension: "")
Alternatively nil as the extension parameter also works.
If you have a URL, and you want to get the name of the file itself for some reason, then you can do this:
soundURL.URLByDeletingPathExtension?.lastPathComponent
Swift 4
let soundURL = NSBundle.mainBundle().URLForResource("soundfile.ext", withExtension: "")
soundURL.deletingPathExtension().lastPathComponent

Works in Swift 5. Adding these behaviors to String class:
extension String {
func fileName() -> String {
return URL(fileURLWithPath: self).deletingPathExtension().lastPathComponent
}
func fileExtension() -> String {
return URL(fileURLWithPath: self).pathExtension
}
}
Example:
let file = "image.png"
let fileNameWithoutExtension = file.fileName()
let fileExtension = file.fileExtension()

Solution Swift 4
This solution will work for all instances and does not depend on manually parsing the string.
let path = "/Some/Random/Path/To/This.Strange.File.txt"
let fileName = URL(fileURLWithPath: path).deletingPathExtension().lastPathComponent
Swift.print(fileName)
The resulting output will be
This.Strange.File

In Swift 2.1 String.pathExtension is not available anymore. Instead you need to determine it through NSURL conversion:
NSURL(fileURLWithPath: filePath).pathExtension

In Swift you can change to NSString to get extension faster:
extension String {
func getPathExtension() -> String {
return (self as NSString).pathExtension
}
}

Latest Swift 4.2 works like this:
extension String {
func fileName() -> String {
return URL(fileURLWithPath: self).deletingPathExtension().lastPathComponent
}
func fileExtension() -> String {
return URL(fileURLWithPath: self).pathExtension
}
}

In Swift 2.1, it seems that the current way to do this is:
let filename = fileURL.URLByDeletingPathExtension?.lastPathComponent
let extension = fileURL.pathExtension

Swift 5 with code sugar
extension String {
var fileName: String {
URL(fileURLWithPath: self).deletingPathExtension().lastPathComponent
}
var fileExtension: String{
URL(fileURLWithPath: self).pathExtension
}
}

SWIFT 3.x Shortest Native Solution
let fileName:NSString = "the_file_name.mp3"
let onlyName = fileName.deletingPathExtension
let onlyExt = fileName.pathExtension
No extension or any extra stuff
(I've tested. based on #gabbler solution for Swift 2)

Swift 5
URL.deletingPathExtension().lastPathComponent

Strings in Swift can definitely by tricky. If you want a pure Swift method, here's how I would do it:
Use find to find the last occurrence of a "." in the reverse of the string
Use advance to get the correct index of the "." in the original string
Use String's subscript function that takes an IntervalType to get the strings
Package this all up in a function that returns an optional tuple of the name and extension
Something like this:
func splitFilename(str: String) -> (name: String, ext: String)? {
if let rDotIdx = find(reverse(str), ".") {
let dotIdx = advance(str.endIndex, -rDotIdx)
let fname = str[str.startIndex..<advance(dotIdx, -1)]
let ext = str[dotIdx..<str.endIndex]
return (fname, ext)
}
return nil
}
Which would be used like:
let str = "/Users/me/Documents/Something.something/text.txt"
if let split = splitFilename(str) {
println(split.name)
println(split.ext)
}
Which outputs:
/Users/me/Documents/Something.something/text
txt
Or, just use the already available NSString methods like pathExtension and stringByDeletingPathExtension.

Swift 5
URL(string: filePath)?.pathExtension

Try this for a simple Swift 4 solution
extension String {
func stripExtension(_ extensionSeperator: Character = ".") -> String {
let selfReversed = self.reversed()
guard let extensionPosition = selfReversed.index(of: extensionSeperator) else { return self }
return String(self[..<self.index(before: (extensionPosition.base.samePosition(in: self)!))])
}
}
print("hello.there.world".stripExtension())
// prints "hello.there"

Swift 3.0
let sourcePath = NSURL(string: fnName)?.pathExtension
let pathPrefix = fnName.replacingOccurrences(of: "." + sourcePath!, with: "")

Swift 3.x extended solution:
extension String {
func lastPathComponent(withExtension: Bool = true) -> String {
let lpc = self.nsString.lastPathComponent
return withExtension ? lpc : lpc.nsString.deletingPathExtension
}
var nsString: NSString {
return NSString(string: self)
}
}
let path = "/very/long/path/to/filename_v123.456.plist"
let filename = path.lastPathComponent(withExtension: false)
filename constant now contains "filename_v123.456"

A better way (or at least an alternative in Swift 2.0) is to use the String pathComponents property. This splits the pathname into an array of strings. e.g
if let pathComponents = filePath.pathComponents {
if let last = pathComponents.last {
print(" The last component is \(last)") // This would be the extension
// Getting the last but one component is a bit harder
// Note the edge case of a string with no delimiters!
}
}
// Otherwise you're out of luck, this wasn't a path name!

They got rid of pathExtension for whatever reason.
let str = "Hello/this/is/a/filepath/file.ext"
let l = str.componentsSeparatedByString("/")
let file = l.last?.componentsSeparatedByString(".")[0]
let ext = l.last?.componentsSeparatedByString(".")[1]

A cleaned up answer for Swift 4 with an extension off of PHAsset:
import Photos
extension PHAsset {
var originalFilename: String? {
if #available(iOS 9.0, *),
let resource = PHAssetResource.assetResources(for: self).first {
return resource.originalFilename
}
return value(forKey: "filename") as? String
}
}
As noted in XCode, the originalFilename is the name of the asset at the time it was created or imported.

Maybe I'm getting too late for this but a solution that worked for me and consider quite simple is using the #file compiler directive. Here is an example where I have a class FixtureManager, defined in FixtureManager.swift inside the /Tests/MyProjectTests/Fixturesdirectory. This works both in Xcode and withswift test`
import Foundation
final class FixtureManager {
static let fixturesDirectory = URL(fileURLWithPath: #file).deletingLastPathComponent()
func loadFixture(in fixturePath: String) throws -> Data {
return try Data(contentsOf: fixtureUrl(for: fixturePath))
}
func fixtureUrl(for fixturePath: String) -> URL {
return FixtureManager.fixturesDirectory.appendingPathComponent(fixturePath)
}
func save<T: Encodable>(object: T, in fixturePath: String) throws {
let data = try JSONEncoder().encode(object)
try data.write(to: fixtureUrl(for: fixturePath))
}
func loadFixture<T: Decodable>(in fixturePath: String, as decodableType: T.Type) throws -> T {
let data = try loadFixture(in: fixturePath)
return try JSONDecoder().decode(decodableType, from: data)
}
}

Creates unique "file name" form url including two previous folders
func createFileNameFromURL (colorUrl: URL) -> String {
var arrayFolders = colorUrl.pathComponents
// -3 because last element from url is "file name" and 2 previous are folders on server
let indx = arrayFolders.count - 3
var fileName = ""
switch indx{
case 0...:
fileName = arrayFolders[indx] + arrayFolders[indx+1] + arrayFolders[indx+2]
case -1:
fileName = arrayFolders[indx+1] + arrayFolders[indx+2]
case -2:
fileName = arrayFolders[indx+2]
default:
break
}
return fileName
}

Related

Show random emoji inside a Label in tableViewCell

I want to have a random emoji inside a Label and every tableViewCell should show a different, random picked emoji...
I tried following function, but sadly it's giving me following error: Value of type '[String]' has no member 'shuffled'
let emojis: [String] = {
let url = Bundle.main.url(forResource: "emojis", withExtension: "txt")!
let list = try! String(contentsOf: url).map { String($0) }
return list.shuffled()
}()
po String(UnicodeScalar(Array(0x1F300...0x1F3F0).randomElement()!)!)
As Carpsen says, the shuffled() function is only in Swift ≤ 4.2. In earlier versions of the language you have to write your own.
Here is an example implementation. I called my scrambled(), so it will work in any version of Swift including 4.2:
extension Array {
func scrambled () -> [Element] {
var source = self
var dest = Array<Element>()
for _ in 1...self.count {
let index = Int(arc4random_uniform(UInt32(source.count)))
dest.append(source.remove(at: index))
}
return dest
}
}
You could scramble the entire set of emojis, but it seems to me you'd be better off creating an array of random emoji when you first populate the data source of your table view. To do that you could use a function randomEmoji():
func randomEmoji() -> String {
let range = 0x1F300...0x1F3F0
let index = Int(arc4random_uniform(UInt32(range.count)))
let ord = range.lowerBound + index
guard let scalar = UnicodeScalar(ord) else { return "❓" }
return String(scalar)
}
So you'd create a stuct to hold all the data for a table view cell, and then populate each struct with a call to randomEmoji().
Note that the randomEmoji() function only chooses emoji in the unicode range from 0x1F300 to 0x1F3F0. Also, the approach of using randomEmoji() may cause duplicate emoji.
You can easily use this extension for emoji string :)
extension NSObject {
public var emojiString: String {
let pointer = Unmanaged.passUnretained(self).toOpaque()
// You can adjust your range
//let range = 0x1F600...0x1F64F
let range = 0x1F300...0x1F3F0
let index = (pointer.hashValue % range.count)
let ord = range.lowerBound + index
guard let scalar = UnicodeScalar(ord) else { return "❓" }
return String(scalar)
}
}
Just call cell.textLabel?.text = cell.emojiString and you're good to go.
Unless you are using Swift 4.2, the shuffled()is not available and the error message is spot on.
You could create your own shuffled() function.

extract out substring but get compiler error

I have a string "John+20", I would like to extract out "John", so, I tried following based on this answer:
// data contains value "John+20"
static func getName(fromString data: String?) {
guard let myData = data else {
return
}
let idx = myData.index(of: "+")
//Compiler ERROR: Generic parameter 'Self' could not be inferred
let name = String(myData[..<idx])
}
But I get the error I mentioned in code comment, why is that?
I am using Swift 4.1 in my iOS project.
I guess the index is an optional as well. Try :
// data contains value "John+20"
static func getName(fromString data: String?) {
guard let myData = data else, let idx = myData.index(of: "+") {
return
}
let name = String(myData[..<idx])
}

JavaScriptCore doesn't work anything else than 'hello, world'

I'm currently writing a Bitcoin-related app in Swift. As there are no native libraries for BIP32/39 available, I decided to go with js ones using JavaScriptCore.
The problem is, almost everything outputs undefined in Swift, e.g.:
var pip_buffer = new Uint8Array(strength / 8);
This one works:
var pip_buffer = "Hello, world"
Here's my Swift code:
var context: JSContext?
private func initJS() {
context = JSContext()
if let jsSrcPath = Bundle.main.path(forResource: "script", ofType: "js") {
do {
let jsSrcContents = try String(contentsOfFile: jsSrcPath)
_ = context?.evaluateScript(jsSrcContents)
} catch let error {
print(error.localizedDescription)
}
}
}
private func getJSVar(name: String) {
if let vb = context?.objectForKeyedSubscript(name) {
print("\(vb)")
}
}
override func viewDidLoad() {
super.viewDidLoad()
initJS()
getJSVar(name: "pip_buffer")
}
How to make this thing work?
Looking at JSValue API Doc I don't see any reference to Uint8Array, which probably means that JavaScriptCore can't convert Uint8Array to a "Swift" value.
You can use Array.from(pip_buffer) on the JS side to convert your Uint8Array to a regular number array, which works.
e.g.
if let vb = context?.evaluateScript("Array.from(pip_buffer)") {
print("\(vb)")
}
Once converted a regular JS number array, you can also use JSValue.toArray() (Swift side), to get it as an array (and can also use toNumber(), toInt32() or toUInt32(), for each item in the array).

How to remove special characters from string in Swift 2?

The answer in
How to strip special characters out of string?
is not working.
Here is what I got and it gives me an error
func removeSpecialCharsFromString(str: String) -> String {
let chars: Set<String> = Set(arrayLiteral: "abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLKMNOPQRSTUVWXYZ1234567890+-*=(),.:!_")
return String(str.characters.filter { chars.contains($0) }) //error here at $0
}
The error at $0 says
_Element (aka Character) cannot be converted to expected argument type 'String'.
Like this:
func removeSpecialCharsFromString(text: String) -> String {
let okayChars : Set<Character> =
Set("abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLKMNOPQRSTUVWXYZ1234567890+-*=(),.:!_".characters)
return String(text.characters.filter {okayChars.contains($0) })
}
And here's how to test:
let s = removeSpecialCharsFromString("père") // "pre"
SWIFT 4:
func removeSpecialCharsFromString(text: String) -> String {
let okayChars = Set("abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLKMNOPQRSTUVWXYZ1234567890+-=().!_")
return text.filter {okayChars.contains($0) }
}
More cleaner way:
extension String {
var stripped: String {
let okayChars = Set("abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLKMNOPQRSTUVWXYZ1234567890+-=().!_")
return self.filter {okayChars.contains($0) }
}
}
Use this extension like:
let myCleanString = "some.Text##$".stripped
Output: "some.Text"
I think that a cleaner solution could be this approach:
extension String {
var alphanumeric: String {
return self.components(separatedBy: CharacterSet.alphanumerics.inverted).joined().lowercased()
}
}
Try this:
someString.removeAll(where: {$0.isPunctuation})
In Swift 1.2,
let chars = Set("abcde...")
created a set containing all characters from the given string.
In Swift 2.0 this has to be done as
let chars = Set("abcde...".characters)
The reason is that a string itself does no longer conform to
SequenceType, you have to use the characters view explicitly.
With that change, your method compiles and works as expected:
func removeSpecialCharsFromString(str: String) -> String {
let chars = Set("abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLKMNOPQRSTUVWXYZ1234567890+-*=(),.:!_".characters)
return String(str.characters.filter { chars.contains($0) })
}
let cleaned = removeSpecialCharsFromString("ab€xy")
print(cleaned) // abxy
Remark: #Kametrixom suggested to create the set only once. So if there is
performance issue with the above method you can either move the
declaration of the set outside of the function, or make it a
local static:
func removeSpecialCharsFromString(str: String) -> String {
struct Constants {
static let validChars = Set("abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLKMNOPQRSTUVWXYZ1234567890+-*=(),.:!_".characters)
}
return String(str.characters.filter { Constants.validChars.contains($0) })
}
without removing spaces between words
extension String {
var removeSpecialCharacters: String {
return self.components(separatedBy: CharacterSet.alphanumerics.inverted).filter({ !$0.isEmpty }).joined(separator: " ")
}
}

encoding url using swift code

I need to send an URL in Arabic language, so I need to encode it before I put it in URL. I am using Swift code.
Below is an example what i really need
var s = "www.example.com/السلام عليكم"
let url = NSURL(string : s)
So the word (السلام عليكم) is in Arabic characters that what I want to send.
Swift 2.0
let urlwithPercentEscapes = myurlstring.stringByAddingPercentEncodingWithAllowedCharacters( NSCharacterSet.URLQueryAllowedCharacterSet())
Swift 3
let urlwithPercentEscapes = myurlstring.addingPercentEncoding( withAllowedCharacters: .urlQueryAllowed)
To improve #Druva's answer
create an extention somewhere in the project
Swift 2.0
extension String
{
func encodeUrl() -> String
{
return self.stringByAddingPercentEncodingWithAllowedCharacters( NSCharacterSet.URLQueryAllowedCharacterSet())
}
func decodeUrl() -> String
{
return self.stringByRemovingPercentEncoding
}
}
Swift 3.0
extension String
{
func encodeUrl() -> String
{
return self.addingPercentEncoding( withAllowedCharacters: .urlQueryAllowed)
}
func decodeUrl() -> String
{
return self.stringByRemovingPercentEncoding
}
}
You need to encode url as you have written. You can do so with that string method:
stringByAddingPercentEscapesUsingEncoding(NSStringEncoding)
So your code will be:
var s = "www.example.com/السلام عليكم"
// you may add check before force unwrapping
let url = NSURL(string : s.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding)!)
You need to encode this string as it contains special characters.
var s = "www.example.com/السلام عليكم"
let encodedLink = s.addingPercentEncoding(withAllowedCharacters: .urlFragmentAllowed)
let encodedURL = NSURL(string: encodedLink!)! as URL
where encodedURL is your final URL
swift 4
we face the same problem it solved by this way
extension String {
var fixedArabicURL: String? {
return self.addingPercentEncoding(withAllowedCharacters: CharacterSet.alphanumerics
.union(CharacterSet.urlPathAllowed)
.union(CharacterSet.urlHostAllowed))
} }
you have to Encode this URL before sending this URL

Resources