How to split filename from file extension in Swift?
Asked Answered
W

21

105

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?

Wallasey answered 3/11, 2014 at 3:29 Comment(2)
Have you looked at the methods of NSString that are specifically for working with paths (like lastPathComponent and stringByDeletingPathExtension)?Avalon
You know, if we Objective-C users have to put up with the vandalism that has been done to the documentation to incorporate Swift, it would be nice if you Swifties looked at it.Hallock
M
92

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
Motmot answered 8/10, 2015 at 7:4 Comment(2)
this should be the recommended answer! works perfectlyAdrenaline
Indeed, and it is documented that you can pass the whole file name and leave the extension nil or empty: developer.apple.com/library/mac/documentation/Cocoa/Reference/…:Savate
L
145

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
Llovera answered 3/11, 2014 at 3:51 Comment(6)
Since Swift’s String type is bridged seamlessly to Foundation’s NSString class, there's no need to create a new NSString. Just use let pathExtension = filename.pathExtension and let pathPrefix = filename.stringByDeletingPathExtension.Bowls
In order to access stringByDeletingPathExtension, you have to cast filename as NSString - let pathPrefix = (filename as NSString).stringByDeletingPathExtension. Same for pathExtension as well.Sande
Update the name changed: stringByDeletingPathExtension is now deletingPathExtensionHaleakala
Looks like you have to let filename: NSString = "bottom_bar.png" as NSString now.Repine
Should be let pathPrefix = filename.deletingPathExtensionPickax
for those starting out with a String you can cast it to NSString as in: let myStr = "bottom_bar.png"; let filename = myStr as NSString ... rest of codeNorwood
M
92

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
Motmot answered 8/10, 2015 at 7:4 Comment(2)
this should be the recommended answer! works perfectlyAdrenaline
Indeed, and it is documented that you can pass the whole file name and leave the extension nil or empty: developer.apple.com/library/mac/documentation/Cocoa/Reference/…:Savate
G
73

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()
Guy answered 15/9, 2016 at 17:51 Comment(2)
Just style, but my preferred the variation: public var fileExtension: String { return NSURL(fileURLWithPath: self).pathExtension.lowercaseString ?? "" } // Assumes we want case insensitivity for .JPG YahoosDovetailed
In Swift 3 you should be using URL not NSURL.Slimy
T
33

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
Teratism answered 29/12, 2016 at 19:32 Comment(1)
This has saved me a lot of custom formatting time, thanks!Greiner
R
27

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

NSURL(fileURLWithPath: filePath).pathExtension
Reserve answered 31/10, 2015 at 7:11 Comment(2)
However, NSString.pathExtension is still available, so you could simply cast it to NSString: (pathString as NSString).pathExtension.Hydromel
This works just fine, however it's better to use URL class instead of NSURLWalsingham
P
14

In Swift you can change to NSString to get extension faster:

extension String {
    func getPathExtension() -> String {
        return (self as NSString).pathExtension
    }
}
Paris answered 28/10, 2015 at 9:25 Comment(0)
T
8

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
    }
}
Tallboy answered 6/3, 2019 at 12:39 Comment(0)
U
7

Swift 5

URL.deletingPathExtension().lastPathComponent
U answered 2/9, 2020 at 13:22 Comment(0)
A
5

In Swift 2.1, it seems that the current way to do this is:

let filename = fileURL.URLByDeletingPathExtension?.lastPathComponent
let extension = fileURL.pathExtension
Amiraamis answered 17/12, 2015 at 2:57 Comment(0)
V
5

Swift 5 with code sugar

extension String {
    var fileName: String {
       URL(fileURLWithPath: self).deletingPathExtension().lastPathComponent
    }

    var fileExtension: String{
       URL(fileURLWithPath: self).pathExtension
    }
}
Vankirk answered 10/5, 2020 at 10:23 Comment(0)
S
4

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)

Subordinate answered 11/1, 2017 at 23:55 Comment(0)
S
4

Swift 5

 URL(string: filePath)?.pathExtension
Subplot answered 23/5, 2019 at 1:7 Comment(0)
R
3

Strings in Swift can definitely by tricky. If you want a pure Swift method, here's how I would do it:

  1. Use find to find the last occurrence of a "." in the reverse of the string
  2. Use advance to get the correct index of the "." in the original string
  3. Use String's subscript function that takes an IntervalType to get the strings
  4. 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.

Ronaronal answered 3/11, 2014 at 3:50 Comment(0)
G
2

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"
Guacin answered 5/12, 2017 at 22:40 Comment(0)
A
1

Swift 3.0

 let sourcePath = NSURL(string: fnName)?.pathExtension
 let pathPrefix = fnName.replacingOccurrences(of: "." + sourcePath!, with: "")
Arleenarlen answered 1/10, 2016 at 6:19 Comment(0)
S
1

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"

Sams answered 4/8, 2017 at 12:26 Comment(0)
A
0

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!
Aculeus answered 20/9, 2015 at 21:20 Comment(0)
C
0

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]
Cobaltous answered 31/12, 2015 at 5:3 Comment(2)
this works only if filename contains exactly one dot. Not working with more dots and crashing without them..Cytoplasm
If there aren't any periods then the file doesn't have a proper extension. If there are multiple periods then you be able to get the extension with let ext = l.last?.componentsSeparatedByString(".").last.Cobaltous
I
0

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.

Inheritance answered 4/2, 2018 at 6:5 Comment(0)
H
0

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)
    }

}
Hamel answered 2/4, 2018 at 3:53 Comment(0)
S
0

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
}
Summerwood answered 26/4, 2018 at 6:55 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.