Constantino Tsarouhas
Constantino Tsarouhas

Reputation: 6886

What characters are allowed in a iOS file name?

I'm looking for a way to make sure a string can be used as a file name under iOS. I'm currently in the section of the code that deletes incompatible characters. I'm wondering if I'm doing it right.

NSString *filename = @"A file name";
fileName = [fileName stringByTrimmingCharactersInSet: [NSCharacterSet controlCharacterSet]];
fileName = [fileName stringByTrimmingCharactersInSet: [NSCharacterSet newlineCharacterSet]];

I'm also wondering if there's already a method that validates a string as a file name.

Thank you for your advice!

Upvotes: 29

Views: 27639

Answers (10)

Abhi Beckert
Abhi Beckert

Reputation: 33359

Use RegEx:

NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"[^a-zA-Z0-9_]+" options:0 error:nil];
filename = [regex stringByReplacingMatchesInString:filename options:0 range:NSMakeRange(0, filename.length) withTemplate:@"-"];

Swift, using \W character class:

let regex = try! NSRegularExpression(pattern: "[\\W]+")
filename = regex.stringByReplacingMatches(in: filename, options: [], range: NSRange(location: 0, length: count), withTemplate: "_")

Upvotes: 32

Johannes Fahrenkrug
Johannes Fahrenkrug

Reputation: 44760

I'm pretty happy with this solution:

NSString *testString = @"This*is::/legal.😀,?縦書き 123";
NSString *result = [[[testString componentsSeparatedByCharactersInSet:[[NSCharacterSet alphanumericCharacterSet] invertedSet]] filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"length > 0"]] componentsJoinedByString:@"-"];

Output:

"This-is-legal-縦書き-123"

What is this sorcery?

Let me break it up into multiple lines so it's clear what's going on:

NSString *testString = @"This*is::/legal.😀,?縦書き 123";
// Get a character set for everything that's NOT alphanumeric.
NSCharacterSet *nonAlphanumericCharacterSet = [[NSCharacterSet alphanumericCharacterSet] invertedSet];
// Split the string on each non-alphanumeric character, thus removing them.
NSArray *cleanedUpComponentsWithBlanks = [testString componentsSeparatedByCharactersInSet:nonAlphanumericCharacterSet];
// Filter out empty strings ("length" is a KVO-compliant property that the predicate can call on each NSString in the array).
NSArray *cleanedUpComponentsWithoutBlanks = [cleanedUpComponentsWithBlanks filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"length > 0"]];
// Put the components back together and join them with a "-".
NSString *result = [cleanedUpComponentsWithoutBlanks componentsJoinedByString:@"-"];

Enjoy!

Swift 4 Version

Added by john-pang on 2021-09-01 with Swift version:

let testString = "This*is::/legal.😀,?縦書き 123"
// Get a character set for everything that's NOT alphanumeric.
let nonAlphanumericCharacterSet = CharacterSet.alphanumerics.inverted
// Split the string on each non-alphanumeric character, thus removing them.
let cleanedUpComponentsWithBlanks = testString.components(separatedBy: nonAlphanumericCharacterSet)
// Filter out empty strings ("length" is a KVO-compliant property that the predicate can call on each NSString in the array).
let cleanedUpComponentsWithoutBlanks = cleanedUpComponentsWithBlanks.filter { !$0.isEmpty }
// Put the components back together and join them with a "-".
let result = cleanedUpComponentsWithoutBlanks.joined(separator: "_")

Upvotes: 3

Mirza Bilal
Mirza Bilal

Reputation: 1050

Swift 5 extension:

I wanted to remove emojis as well and in windows \ is also an invalid character. So I added symbols charset and backslash \ as well.

extension String {
    var validFilename: String {
        let invalidCharsets = CharacterSet(charactersIn: ":/\\")
            .union(.illegalCharacters)
            .union(.controlCharacters)
            .union(.symbols)
            .union(.newlines)
        return self.components(separatedBy: invalidCharsets).joined()
    }
}

Upvotes: 4

STerrier
STerrier

Reputation: 4015

Base on Marian Answers, here is a string extension to remove any unwanted characters.

extension String {

func stripCharacters() -> String {
    var invalidCharacters = CharacterSet(charactersIn: ":/")
    invalidCharacters.formUnion(.newlines)
    invalidCharacters.formUnion(.illegalCharacters)
    invalidCharacters.formUnion(.controlCharacters)

    let newString = self
        .components(separatedBy: invalidCharacters)
        .joined(separator: "_")

    return newString
   }
}

Example:
let fileName = "Man(lop23/45"
let newFileName = fileName.stripCharacters()
print(newFileName)

Upvotes: 1

Groot
Groot

Reputation: 14251

This String extension (Swift 4.2) will help convert an invalid iOS file name to a valid iOS file name.

 extension String {
    func convertToValidFileName() -> String {
        let invalidFileNameCharactersRegex = "[^a-zA-Z0-9_]+"
        let fullRange = startIndex..<endIndex
        let validName = replacingOccurrences(of: invalidFileNameCharactersRegex,
                                           with: "-",
                                        options: .regularExpression,
                                          range: fullRange)
        return validName
    }
}

For example

"name.name?/!!23$$@1asd".convertToValudFileName()       // "name-name-23-1asd"

"!Hello.312,^%-0//\r\r".convertToValidFileName()        // "-Hello-312-0-"

"/foo/bar/pop?soda=yes|please".convertToValidFileName() // "-foo-bar-pop-soda-yes-please"

Upvotes: 10

Mari&#225;n Čern&#253;
Mari&#225;n Čern&#253;

Reputation: 15748

I find this to be cleaner and probably much more performant. This is based on Angel Naydenov's solution, but first constructing Character set with all invalid characters and then calling components(separatedBy:) just once.

Swift 3 & 4

var invalidCharacters = CharacterSet(charactersIn: ":/")
invalidCharacters.formUnion(.newlines)
invalidCharacters.formUnion(.illegalCharacters)
invalidCharacters.formUnion(.controlCharacters)

let newFilename = originalFilename
    .components(separatedBy: invalidCharacters)
    .joined(separator: "")

Swift 2

let invalidCharacters = NSMutableCharacterSet(charactersInString: ":/")
invalidCharacters.formUnionWithCharacterSet(NSCharacterSet.newlineCharacterSet())
invalidCharacters.formUnionWithCharacterSet(NSCharacterSet.illegalCharacterSet())
invalidCharacters.formUnionWithCharacterSet(NSCharacterSet.controlCharacterSet())

let filename = originalFilename
    .componentsSeparatedByCharactersInSet(invalidCharacters)
    .joinWithSeparator("")

Upvotes: 22

Sebastian Boldt
Sebastian Boldt

Reputation: 5321

I came up with the following solution. Works nice so far.

import Foundation

extension String {
    func removeUnsupportedCharactersForFileName() -> String {
        var cleanString = self
        ["?", "/", "\\", "*"].forEach {
            cleanString = cleanString.replacingOccurrences(of: $0, with: "-")
        }
        return cleanString
    }
}

let a = "***???foo.png"
let validString = a.removeUnsupportedCharactersForFileName()

Upvotes: 1

Angel Naydenov
Angel Naydenov

Reputation: 1665

As I did not see a list with allowed characters in this question but the question wanted a list with such characters I am adding a bit more details on this topic.

First we need to know what is the file system that iOS devices use. Using multiple online sources this seems to be HFSX which is the HFS+ case sensitive version. And including one link here for reference: https://apple.stackexchange.com/questions/83671/what-filesystem-does-ios-use

Now that we know what the file system is we can look for what characters are not allowed. And these seem to be: colon (:) and slash (/). Here is a link for reference: http://www.comentum.com/File-Systems-HFS-FAT-UFS.html

Having this information and what others have written in this thread my personal preference for removing not allowed characters from file names is the following Swift code:

filename = "-".join(filename.componentsSeparatedByCharactersInSet(NSCharacterSet.newlineCharacterSet()))
filename = "-".join(filename.componentsSeparatedByCharactersInSet(NSCharacterSet.illegalCharacterSet()))
filename = "-".join(filename.componentsSeparatedByCharactersInSet(NSCharacterSet.controlCharacterSet()))
filename = "-".join(filename.componentsSeparatedByString(":"))
filename = "-".join(filename.componentsSeparatedByString("/"))

The reason I am not preferring the RegEx approach is that it seems too restrictive to me. I do not want to restrict my users only to Latin characters. They may as well wish to use some Chinese, Cyrillic or whatever else they like.

Happy coding!

Upvotes: 11

Erik B
Erik B

Reputation: 42574

First of all, you're using the wrong method. Trimming the string will only remove characters in the beginning and the end of the string.

What you're looking for is something more like:

fileName = [fileName stringByReplacingOccurrencesOfString:@"/" withString:@"_"];

However, that's a suboptimal solution, since you'll have to do that for every character you want to exclude, so maybe you want to keep looking or write you're own method for manipulating the string.

iOS is UNIX based and as such I suppose it supports almost any characters in filenames. UNIX allows white spaces, <, >, |, \, :, (, ), &, ;, as well as wildcards such as ? and *, to be quoted or escaped using \ symbol. However I wouldn't use any of those characters in my filenames. In fact, I would restrict the characters in my filenames to 'a'-'z', '0'-'9', '_' and '.'.

Upvotes: 12

Lmd64
Lmd64

Reputation: 146

I've had to save remote files locally with filenames containing other characters than basic alpha-numeric characters. I use the method below to strip out potential invalid characters, ensuring it's a valid filename for the filesystem when generating a NSURL using URLWithString:

    filename = [[filename componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] componentsJoinedByString:@"" ];
    filename = [[filename componentsSeparatedByCharactersInSet:[NSCharacterSet illegalCharacterSet]] componentsJoinedByString:@"" ];
    filename = [[filename componentsSeparatedByCharactersInSet:[NSCharacterSet symbolCharacterSet]] componentsJoinedByString:@"" ];
    fileURLString = [NSTemporaryDirectory() stringByAppendingPathComponent:filename];
    fileURL = [NSURL URLWithString:fileURLString];

You may also want to test for collision errors first using:

    [[NSFileManager defaultManager] fileExistsAtPath:[fileURL absoluteString]]

Upvotes: 9

Related Questions