Reputation: 7337
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?
Upvotes: 107
Views: 110126
Reputation: 13766
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
Upvotes: 150
Reputation: 649
Swift 5 with code sugar
extension String {
var fileName: String {
URL(fileURLWithPath: self).deletingPathExtension().lastPathComponent
}
var fileExtension: String{
URL(fileURLWithPath: self).pathExtension
}
}
Upvotes: 5
Reputation: 7637
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()
Upvotes: 73
Reputation: 81
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
}
}
Upvotes: 8
Reputation: 11
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
}
Upvotes: 0
Reputation: 700
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
Upvotes: 33
Reputation: 2211
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 with
swift 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)
}
}
Upvotes: 0
Reputation: 36620
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.
Upvotes: 0
Reputation: 2907
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
Upvotes: 94
Reputation: 871
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"
Upvotes: 2
Reputation: 339
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"
Upvotes: 1
Reputation: 1059
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)
Upvotes: 4
Reputation: 141
In Swift you can change to NSString
to get extension faster:
extension String {
func getPathExtension() -> String {
return (self as NSString).pathExtension
}
}
Upvotes: 14
Reputation: 8995
Swift 3.0
let sourcePath = NSURL(string: fnName)?.pathExtension
let pathPrefix = fnName.replacingOccurrences(of: "." + sourcePath!, with: "")
Upvotes: 1
Reputation: 942
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]
Upvotes: 0
Reputation: 1433
In Swift 2.1, it seems that the current way to do this is:
let filename = fileURL.URLByDeletingPathExtension?.lastPathComponent
let extension = fileURL.pathExtension
Upvotes: 5
Reputation: 1076
In Swift 2.1 String.pathExtension is not available anymore. Instead you need to determine it through NSURL conversion:
NSURL(fileURLWithPath: filePath).pathExtension
Upvotes: 27
Reputation: 235
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!
Upvotes: 0
Reputation: 42325
Strings in Swift can definitely by tricky. If you want a pure Swift method, here's how I would do it:
find
to find the last occurrence of a "."
in the reverse
of the stringadvance
to get the correct index of the "."
in the original stringString
's subscript
function that takes an IntervalType
to get the stringsSomething 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
.
Upvotes: 3