NSFileManager unique file names - objective-c

I need a quick and easy way to store files with unique file names on iOS. I need to prefix the file with a string, and then append the generated unique identifier to the end. I was hoping NSFileManager had some convenient method to do this, but I can't seem to find it.
I was looking at createFileAtPath:contents:attributes:, but am unsure if the attributes will give me that unique file name.

Create your own file name:
CFUUIDRef uuid = CFUUIDCreate(NULL);
CFStringRef uuidString = CFUUIDCreateString(NULL, uuid);
CFRelease(uuid);
NSString *uniqueFileName = [NSString stringWithFormat:#"%#%#", prefixString, (NSString *)uuidString];
CFRelease(uuidString);
A simpler alternative proposed by #darrinm in the comments:
NSString *prefixString = #"MyFilename";
NSString *guid = [[NSProcessInfo processInfo] globallyUniqueString] ;
NSString *uniqueFileName = [NSString stringWithFormat:#"%#_%#", prefixString, guid];
NSLog(#"uniqueFileName: '%#'", uniqueFileName);
NSLog output:
uniqueFileName: 'MyFilename_680E77F2-20B8-444E-875B-11453B06606E-688-00000145B460AF51'
Note: iOS6 introduced the NSUUID class which can be used in place of CFUUID.
NSString *guid = [[NSUUID new] UUIDString];

Super-easy Swift 4 1-liner:
fileName = "MyFileName_" + UUID().uuidString
or
fileName = "MyFileName_" + ProcessInfo().globallyUniqueString

I use current date to generate random file name with a given extension. This is one of the methods in my NSFileManager category:
+ (NSString*)generateFileNameWithExtension:(NSString *)extensionString
{
// Extenstion string is like #".png"
NSDate *time = [NSDate date];
NSDateFormatter* df = [NSDateFormatter new];
[df setDateFormat:#"dd-MM-yyyy-hh-mm-ss"];
NSString *timeString = [df stringFromDate:time];
NSString *fileName = [NSString stringWithFormat:#"File-%#%#", timeString, extensionString];
return fileName;
}

You can also use the venerable mktemp() (see man 3 mktemp). Like this:
- (NSString*)createTempFileNameInDirectory:(NSString*)dir
{
NSString* templateStr = [NSString stringWithFormat:#"%#/filename-XXXXX", dir];
char template[templateStr.length + 1];
strcpy(template, [templateStr cStringUsingEncoding:NSASCIIStringEncoding]);
char* filename = mktemp(template);
if (filename == NULL) {
NSLog(#"Could not create file in directory %#", dir);
return nil;
}
return [NSString stringWithCString:filename encoding:NSASCIIStringEncoding];
}
The XXXXX will be replaced with a unique letter/number combination. They can only appear at the end of the template, so you cannot have an extension appended in the template (though you can append it after the unique file name is obtained). Add as many X as you want in the template.
The file is not created, you need to create it yourself. If you have multiple threads creating unique files in the same directory, you run the possibility of having race conditions. If this is the case, use mkstemp() which creates the file and returns a file descriptor.

In iOS 6 the simplest method is to use:
NSString *uuidString = [[NSUUID UUID] UUIDString];

Here is what I ended up using in Swift 3.0
public func generateUniqueFilename (myFileName: String) -> String {
let guid = ProcessInfo.processInfo.globallyUniqueString
let uniqueFileName = ("\(myFileName)_\(guid)")
print("uniqueFileName: \(uniqueFileName)")
return uniqueFileName
}

Swift 4.1 and 5. Just pass you file extension name and function will return unique file name.
func uniqueFileNameWithExtention(fileExtension: String) -> String {
let uniqueString: String = ProcessInfo.processInfo.globallyUniqueString
let formatter = DateFormatter()
formatter.dateFormat = "yyyyMMddhhmmsss"
let dateString: String = formatter.string(from: Date())
let uniqueName: String = "\(uniqueString)_\(dateString)"
if fileExtension.length > 0 {
let fileName: String = "\(uniqueName).\(fileExtension)"
return fileName
}
return uniqueName
}

This should probably work for you:
http://vgable.com/blog/2008/02/24/creating-a-uuid-guid-in-cocoa/
The author of the post suggests implementing a 'stringWithUUID' method as a category of NSString. Just append a GUID generated with this method to the end of the file name that you're creating.

Swift 4.2, I use two options, one mostly unique but readable, and the other just unique.
// Create a unique filename, added to a starting string or not
public func uniqueFilename(filename: String = "") -> String {
let uniqueString = ProcessInfo.processInfo.globallyUniqueString
return filename + "-" + uniqueString
}
// Mostly Unique but Readable ID based on date and time that is URL compatible ("unique" to nearest second)
public func uniqueReadableID(name: String = "") -> String {
let timenow = DateFormatter.localizedString(from: Date(), dateStyle: .medium, timeStyle: .medium)
let firstName = name + "-" + timenow
do {
// Make ID compatible with URL usage
let regex = try NSRegularExpression(pattern: "[^a-zA-Z0-9_]+", options: [])
let newName = regex.stringByReplacingMatches(in: firstName, options: [], range: NSMakeRange(0, firstName.count), withTemplate: "-")
return newName
}
catch {
print("🧨 Unique ID Error: \(error.localizedDescription)")
return uniqueFilename(filename: name)
}
}

Related

What does [urlString componentsSeperatedByString: #"?"] [0]? mean in Swift?

I'm not entirely sure what
NSString * fileName = [self cachedFileNameForKey:[urlString componentsSeparatedByString:#"?"][0]];
Means from this code. I am primely writing in Swift so this notation is a bit confusing for me. What does the double [ ] notation mean? 3d array?
+ (AVPlayerItem *)localDownloadedVideoFromUrl:(NSURL *)url {
NSString * urlString = url.absoluteString;
NSString * fileName = [self cachedFileNameForKey:[urlString componentsSeparatedByString:#"?"][0]];
TWRDownloadManager * manager = [TWRDownloadManager sharedManager];
if ([manager fileExistsWithName:fileName]) {
AVPlayerItem * item = [AVPlayerItem playerItemWithURL:[NSURL fileURLWithPath:[manager localPathForFile:fileName]]];
return item;
}
return [NSNull null];
}
[] in objective-c is not only used to access arrays, but also used to call methods. Surprising, isn't it?
In general,
[xxx someMethod];
is equivalent to:
xxx.someMethod()
in swift.
So here:
[urlString componentsSeparatedByString:#"?"][0]
means
urlString.components(separatedBy: "?")[0] // "[0]" can also be replaced by ".first", which is safer.
This is then passed to the cachedFileNameForKey as a parameter.
In Swift
var urlString: String = "Know someone who can answer? Share a link to this" // Example string or url.absoluteString
let fileName = urlString.components(separatedBy: "?")
print(fileName)
let string1 = fileName[0]
let string2 = fileName[1]
Hope will helpful to you
Ahh so mistake on my part is that I didn't see what the method -cacheFileNameForKey: was doing.
+ (nullable NSString *)cachedFileNameForKey:(nullable NSString *)key {
const char *str = key.UTF8String;
if (str == NULL) {
str = "";
}
unsigned char r[CC_MD5_DIGEST_LENGTH];
CC_MD5(str, (CC_LONG)strlen(str), r);
NSString *filename = [NSString stringWithFormat:#"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%#",
r[0], r[1], r[2], r[3], r[4], r[5], r[6], r[7], r[8], r[9], r[10],
r[11], r[12], r[13], r[14], r[15], [key.pathExtension isEqualToString:#""] ? #"" : [NSString stringWithFormat:#".%#", key.pathExtension]];
return filename;
}
I guess its used for a function to generate a file name for a temporary movie file.

Extract from NSDictionary object

I used json-framework to pull a JSON string from and URL and convert the JSON string to NSDictionary object with these two lines of code
SBJsonParser* parser = [SBJsonParser new];
NSDictionary* myDict = [parser objectWithString:resp];
My NSDicitonary has:
(
{
condition = no;
date = "2013-06-21";
"location_id" = 9;
name = Chabahil;
reason = "";
time = "03:04:22";
},
{
condition = pressure;
date = "2013-06-21";
"location_id" = 7;
name = Maitighar;
reason = "Peak Hour";
time = "03:04:13";
}
)
Now I need to access each element for example I want to get value of "name" of the second element. I couldnot figure out how to do it.
Thanks!
The JSON string contains not a dictionary, but an array (of two dictionaries).
So you would do
SBJsonParser* parser = [SBJsonParser new];
NSArray* jsonArray = [parser objectWithString:resp];
and access the values for example like
NSString *secondName = [[jsonArray objectAtIndex:1] objectForKey:#"name"];
or, using the modern subscripting syntax:
NSString *secondName = jsonArray[1][#"name"];
(Note that there already is a NSJSONSerialization class in Foundation, so unless you
have a specific reason to use SBJsonParser, you could use that as well.)

Objective-C, get string from text file?

I know there are a few different ways to find text in file, although I haven't found a way to return the text after the string I'm searching for. For example, if I was to search file.txt for the term foo and wanted to return bar, how would I do that without knowing it's bar or the length?
Here's the code I'm using:
if (!fileContentsString) {
NSLog(#"Error reading file");
}
// Create the string to search for
NSString *search = #"foo";
// Search the file contents for the given string, put the results into an NSRange structure
NSRange result = [fileContentsString rangeOfString:search];
// -rangeOfString returns the location of the string NSRange.location or NSNotFound.
if (result.location == NSNotFound) {
// foo not found. Bail.
NSLog(#"foo not found in file");
return;
}
// Continue processing
NSLog(#"foo found in file");
}
you could use [NSString substringFromIndex:]
if (result.location == NSNotFound)
{
// foo not found. Bail.
NSLog(#"foo not found in file");
return;
}
else
{
int startingPosition = result.location + result.length;
NSString* foo = [fileContentsString substringFromIndex:startingPosition]
NSLog(#"found foo = %#",foo);
}
You might want to use RegexKitLite and perform a regex look up:
NSArray * captures = [myFileString componentsMatchedByRegex:#"foo\\s+(\\w+)"];
NSString * wordAfterFoo = captures[1];
Not test though.

Insert or split string at uppercase letters objective-c

What would be the most efficient way to convert a string like "ThisStringIsJoined" to "This String Is Joined" in objective-c?
I receive strings like this from a web service thats out of my control and I would like to present the data to the user, so I would just like to tidy it up a bit by adding spaces infront of each uppercase word. The strings are always formatted with each word beginning in an uppercase letter.
I'm quite new to objective-c so cant really figure this one out.
Thanks
One way of achieving this is as follows:
NSString *string = #"ThisStringIsJoined";
NSRegularExpression *regexp = [NSRegularExpression
regularExpressionWithPattern:#"([a-z])([A-Z])"
options:0
error:NULL];
NSString *newString = [regexp
stringByReplacingMatchesInString:string
options:0
range:NSMakeRange(0, string.length)
withTemplate:#"$1 $2"];
NSLog(#"Changed '%#' -> '%#'", string, newString);
The output in this case would be:
'ThisStringIsJoined' -> 'This String Is Joined'
You might want to tweak the regular expression to you own needs. You might want to make this into a category on NSString.
NSRegularExpressions are the way to go, but as trivia, NSCharacterSet can also be useful:
- (NSString *)splitString:(NSString *)inputString {
int index = 1;
NSMutableString* mutableInputString = [NSMutableString stringWithString:inputString];
while (index < mutableInputString.length) {
if ([[NSCharacterSet uppercaseLetterCharacterSet] characterIsMember:[mutableInputString characterAtIndex:index]]) {
[mutableInputString insertString:#" " atIndex:index];
index++;
}
index++;
}
return [NSString stringWithString:mutableInputString];
}
Here's a category on NSString that will do what you want. This will handle non-ASCII letters. It will also split "IDidAGoodThing" properly.
#implementation NSString (SeparateCapitalizedWords)
-(NSString*)stringBySeparatingCapitalizedWords
{
static NSRegularExpression * __regex ;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSError * error = nil ;
__regex = [ NSRegularExpression regularExpressionWithPattern:#"[\\p{Uppercase Letter}]" options:0 error:&error ] ;
if ( error ) { #throw error ; }
});
NSString * result = [ __regex stringByReplacingMatchesInString:self options:0 range:(NSRange){ 1, self.length - 1 } withTemplate:#" $0" ] ;
return result ;
}
#end
Here is Swift Code (objective c code by webstersx), Thanks !
var str: NSMutableString = "iLoveSwiftCode"
var str2: NSMutableString = NSMutableString()
for var i:NSInteger = 0 ; i < str.length ; i++ {
var ch:NSString = str.substringWithRange(NSMakeRange(i, 1))
if(ch .rangeOfCharacterFromSet(NSCharacterSet.uppercaseLetterCharacterSet()).location != NSNotFound) {
str2 .appendString(" ")
}
str2 .appendString(ch)
}
println("\(str2.capitalizedString)")
}
Output : I Love Swift Code
For anyone who came here looking for the similar question answered in Swift:
Perhaps a cleaner (adding to Sankalp's answer), and more 'Swifty' approach:
func addSpaces(to givenString: String) -> String{
var string = givenString
//indexOffset is needed because each time replaceSubrange is called, the resulting count is incremented by one (owing to the fact that a space is added to every capitalised letter)
var indexOffset = 0
for (index, character) in string.characters.enumerated(){
let stringCharacter = String(character)
//Evaluates to true if the character is a capital letter
if stringCharacter.lowercased() != stringCharacter{
guard index != 0 else { continue } //"ILoveSwift" should not turn into " I Love Swift"
let stringIndex = string.index(string.startIndex, offsetBy: index + indexOffset)
let endStringIndex = string.index(string.startIndex, offsetBy: index + 1 + indexOffset)
let range = stringIndex..<endStringIndex
indexOffset += 1
string.replaceSubrange(range, with: " \(stringCharacter)")
}
}
return string
}
You call the function like so:
var string = "iLoveSwiftCode"
addSpaces(to: string)
//Result: string = "i Love Swift Code"
Alternatively, if you prefer extensions:
extension String{
mutating func seperatedWithSpaces(){
//indexOffset is needed because each time replaceSubrange is called, the resulting count is incremented by one (owing to the fact that a space is added to every capitalised letter)
var indexOffset = 0
for (index, character) in characters.enumerated(){
let stringCharacter = String(character)
if stringCharacter.lowercased() != stringCharacter{
guard index != 0 else { continue } //"ILoveSwift" should not turn into " I Love Swift"
let stringIndex = self.index(self.startIndex, offsetBy: index + indexOffset)
let endStringIndex = self.index(self.startIndex, offsetBy: index + 1 + indexOffset)
let range = stringIndex..<endStringIndex
indexOffset += 1
self.replaceSubrange(range, with: " \(stringCharacter)")
}
}
}
}
Call the method from a string:
var string = "iLoveSwiftCode"
string.seperatedWithSpaces()
//Result: string = "i Love Swift Code"
You could try making a new string that is a lowercase copy of the original string. Then compare the two strings and insert spaces wherever the characters are different.
Use the NSString method to turn to lowercase.
- (NSString *)lowercaseString

Possible to use variables and/or parameters with NSLocalizedString?

I have tried using a variable as an input parameter to NSLocalizedString, but all I am getting back is the input parameter. What am I doing wrong? Is it possible to use a variable string value as an index for NSLocalized string?
For example, I have some strings that I want localized versions to be displayed. However, I would like to use a variable as a parameter to NSLocalizedString, instead of a constant string. Likewise, I would like to include formatting elements in the parameter for NSLocalizedString, so I would be able to retrieved a localized version of the string with the same formatting parameters. Can I do the following:
Case 1: Variable NSLocalizedstring:
NSString *varStr = #"Index1";
NSString *string1 = NSLocalizedString(varStr,#"");
Case 2: Formatted NSLocalizedString:
NSString *string1 = [NSString stringWithFormat:NSLocalizedString(#"This is an %#",#""),#"Apple"];
(Please note that the variable can contain anything, not just a fixed set of strings.)
Thanks!
If what you want is to return the localized version of "This is an Apple/Orange/whatever", you'd want:
NSString *localizedVersion = NSLocalizedString(([NSString stringWithFormat:#"This is an %#", #"Apple"]), nil);
(I.e., the nesting of NSLocalizedString() and [NSString stringWithFormat:] are reversed.)
If what you want is the format to be localized, but not the substituted-in value, do this:
NSString *finalString = [NSString stringWithFormat:NSLocalizedString(#"SomeFormat", nil), #"Apple"];
And in your Localizable.strings:
SomeFormat = "This is an %#";
I just want to add one very helpful definition which I use in many of my projects.
Inspired by androids possibility, I've added this function to my header prefix file:
#define NSLocalizedFormatString(fmt, ...) [NSString stringWithFormat:NSLocalizedString(fmt, nil), __VA_ARGS__]
This allows you to define a localized string like the following:
"ExampleScreenAuthorizationDescriptionLbl"= "I authorize the payment of %# to %#.";
and it can be used via:
self.labelAuthorizationText.text = NSLocalizedFormatString(#"ExampleScreenAuthorizationDescriptionLbl", self.formattedAmount, self.companyQualifier);
For swift :
let myString = String(format: NSLocalizedString("I authorize the payment of %d ", comment: ""), amount)
extension String {
public var localizedString: String {
return NSLocalizedString(self, comment: "")
}
public func localizedString(with arguments: [CVarArg]) -> String {
return String(format: localizedString, arguments: arguments)
}
}
Localizable.string:
"Alarm:Popup:DismissOperation:DeviceMessage" = "\"%#\" will send position updates on a regular basis again.";
"Global:Text:Ok" = "OK";
Usage:
let message = "Alarm:Popup:DismissOperation:DeviceMessage".localizedString(with: [name])
and
let title = "Global:Text:Ok".localizedString
It turns out that a missing target entry is to blame. Just checking that my current build target includes the Localizable.string file solved the problem!
If you have more than one variable in your localized string can you use this solution:
In Localizable.strings
"winpopup" = "#name# wins a #type# and get #points# points(s)";
And use stringByReplacingOccurrencesOfString to insert the values
NSString *string = NSLocalizedString(#"winpopup", nil); //"#name# wins a #type# and get #points# points(s)"
NSString *foo = [string stringByReplacingOccurrencesOfString:#"#name#" withString:gameLayer.turn];
NSString *fooo = [foo stringByReplacingOccurrencesOfString:#"#type#" withString:winMode];
NSString *msg = [fooo stringByReplacingOccurrencesOfString:#"#points#" withString:[NSString stringWithFormat:#"%i", pkt]];
NSLog(#"%#", msg);
Your ideas should work. But if you are getting back the input parameter, that means that the input parameter was not found as a key in your Localizable.strings file. Check the syntax and location of that file.
This works for me:
NSMutableString *testMessage = [NSMutableString stringWithString:NSLocalizedString(#"Some localized text", #"")];
testMessage = [NSMutableString stringWithString:[testMessage stringByAppendingString:someStringVariable]];