Is NSUserName() not working in xcode6? - objective-c

NSUserName() doesn't seem to be working for me in Xcode 6 while developing iOS apps.
I was using it to get the desktop directory to save a core data seed to my desktop during development.
NSString* deskStorePath = [NSString stringWithFormat:#"/Users/%#/Desktop/newMySQL.CDBStore", NSUserName()];
I had to change my code to:
#define USER_NAME #"myusername"
NSString* deskStorePath02 = [NSString stringWithFormat:#"/Users/%#/Desktop/newMySQL.CDBStore", USER_NAME];
Now NSUserName() returns nothing in Xcode 6, but had worked fine in Xcode 5.
NSString* myname = NSUserName();
NSString* myfullname = NSFullUserName();
NSLog(#"name : %# - %#",myname,myfullname);
Is it a bug?
Do I need to import a library now?
Or is there an alternative to get the current OSX username during app development?
edit: (This is the method I'm using to save the core data seed store to desktop which I then add to my dev environment)
- (void) saveSeedToDesktop
{
[self testNSUser];
NSString* myDBName = [NSString stringWithFormat:#"%#.sqlite",CD_FILE_NAME];
NSString *storePath = [[self applicationDocumentsDirectory] stringByAppendingPathComponent : myDBName];
NSString* deskStorePath = [NSString stringWithFormat:#"/Users/%#/Desktop/newMySQL.CDBStore", USER_NAME];
NSError*error;
if (![[NSFileManager defaultManager] copyItemAtPath:storePath toPath: deskStorePath error:&error]){
NSLog(#"error with path : %#",error);
}
else {
NSLog(#"Copied");
}
}
- (NSString *)applicationDocumentsDirectory {
return [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES) lastObject];
}
This is a test class:
- (void) testNSUser {
NSString* myname = NSUserName();
NSString* myfullname = NSFullUserName();
NSLog(#"name : %# - %#",myname,myfullname);
__unused NSString* deskStorePathTest01 = [NSString stringWithFormat:#"/Users/this_is_my_name/Desktop/test.txt"]; //works
__unused NSString* deskStorePathTest02 = [NSString stringWithFormat:#"~/Desktop/test.txt"];
__unused NSString* deskStorePathTest03 = [NSString stringWithFormat:#"/Users/~/Desktop/test.txt"];
__unused NSString* deskStorePathTest04 = [NSString stringWithFormat:#"/~/Desktop/test.txt"];
[self fileExistsTest:deskStorePathTest01];
[self fileExistsTest:deskStorePathTest02];
[self fileExistsTest:deskStorePathTest03];
[self fileExistsTest:deskStorePathTest04];
}
- (void) fileExistsTest : (NSString*)storePath {
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *pathForFile = storePath;
if ([fileManager fileExistsAtPath:pathForFile]){
NSLog(#"exists : %#",storePath);
}
else {
NSLog(#"doesnt exist");
}
}

We had the same issue with assets produced by running the app in the simulator. I realised that the app itself is stored below my home directory so I could access my username from that:
NSString *userName = NSUserName();
#if TARGET_IPHONE_SIMULATOR
if (userName.length == 0)
{
NSArray *bundlePathComponents = [NSBundle.mainBundle.bundlePath pathComponents];
if (bundlePathComponents.count >= 3
&& [bundlePathComponents[0] isEqualToString:#"/"]
&& [bundlePathComponents[1] isEqualToString:#"Users"])
{
userName = bundlePathComponents[2];
}
}
#endif
Obviously for development only.

It's working for me, on the device, anyway. It does not seem to work on the simulator.
-(id) init
{
self = [super initWithStyle:UITableViewStyleGrouped];
if(self)
{
//Custom initialization.
NSString *u = NSUserName();
NSLog(#"%#", u);
}
return self;
}
Output is:
2014-10-05 20:45:38.451 [my app business] mobile
If I'm understanding what you're trying to accomplish here, though, I think the prescribed way of initializing the Core Data store with default data may be to programmatically add it. At least, that's what I took away from the docs. I'm pretty sure they say somewhere that you're not supposed to manipulate the Core Data store file outside of the API.

Related

run applescript from cocoa app stopped working

This code had been working fine until just recently. I hadn't' changed anything nor upgraded my system and I'm completely flummoxed.
I've been using it for 6 years and now it dies on me.
Is there an easier or better way of running an applescript from within a cocoa application? At this point I'm happy to pay to fix this problem!
utils.h
#import <Foundation/Foundation.h>
#interface Utils : NSObject
// Runs an applescript with a given map of variables (name/value)
+ (NSArray *)runApplescript:(NSString *)source withVariables:(NSDictionary *)variables;
// Runs an applescript from a file pathwith a given map of variables
// (name/value)
+ (NSArray *)runApplescriptFromFile:(NSString *)scriptName withVariables:(NSDictionary *)variables;
+ (NSArray *)arrayFromDescriptor:(NSAppleEventDescriptor *)descriptor;
// String is empty or only has white characters (space, tab...)
+ (BOOL)stringIsEmptyOrWhite:(NSString *)string;
#end
Utils.M
#import "Utils.h"
#implementation Utils
+ (NSArray *)arrayFromDescriptor:(NSAppleEventDescriptor *)descriptor {
// Enumerate the apple descriptors (lists) returned by the applescript and
// make them into arrays
NSMutableArray *returnArray = [NSMutableArray array];
NSInteger counter, count = [descriptor numberOfItems];
for (counter = 1; counter <= count; counter++) {
NSAppleEventDescriptor *desc = [descriptor descriptorAtIndex:counter];
if (nil != [desc descriptorAtIndex:1]) {
[returnArray addObject:[Utils arrayFromDescriptor:desc]];
} else {
NSString *stringValue = [[descriptor descriptorAtIndex:counter] stringValue];
if (nil != stringValue) {
[returnArray addObject:stringValue];
} else {
[returnArray addObject:#""];
}
}
}
return returnArray;
}
+ (NSString *)escapeCharacters:(NSString *)string {
return [string stringByReplacingOccurrencesOfString:#"\"" withString:#"\\\""];
}
+ (NSArray *)runApplescript:(NSString *)source withVariables:(NSDictionary *)variables {
NSString *input = #"";
NSArray *variableNames = [variables allKeys];
// Transform the dictionary of names/values to set sentences of applescript
for (NSString *variableName in variableNames) {
NSObject *variableValue = [variables objectForKey:variableName];
if ([variableValue isKindOfClass:[NSString class]]) {
input =
[input stringByAppendingString:[NSString stringWithFormat:#"set %# to (\"%#\" as text)\n", variableName,
[Utils escapeCharacters:variableValue], nil]];
} else if ([variableValue isKindOfClass:[NSNumber class]]) {
input = [input stringByAppendingString:[NSString stringWithFormat:#"set %# to (%# as integer)\n",
variableName, variableValue, nil]];
} else if ([variableValue isKindOfClass:[NSArray class]]) {
// Initialize a list
NSString *entry;
NSArray *values = (NSArray *)variableValue;
input = [input stringByAppendingString:[NSString stringWithFormat:#"set %# to {", variableName]];
BOOL first = TRUE;
for (entry in values) {
if (!first) {
input = [input stringByAppendingString:#", "];
}
input = [input
stringByAppendingString:[NSString stringWithFormat:#"\"%#\"", [Utils escapeCharacters:entry], nil]];
first = FALSE;
}
input = [input stringByAppendingString:#"}\n"];
}
}
NSString *finalScript = [input stringByAppendingString:[NSString stringWithFormat:#"\n\n%#", source]];
NSLog(#"Final script: %#", finalScript);
NSAppleScript *script = [[NSAppleScript alloc] initWithSource:finalScript];
NSDictionary *error;
NSAppleEventDescriptor *descriptor = [script executeAndReturnError:&error];
NSLog(#"applescript error: %#", [error description]);
// Transform the return value of applescript to nested nsarrays
return [Utils arrayFromDescriptor:descriptor];
}
+ (NSArray *)runApplescriptFromFile:(NSString *)scriptName withVariables:(NSDictionary *)variables {
NSString *scriptPath = [[NSBundle mainBundle] pathForResource:scriptName ofType:#"applescript"];
NSString *scriptSource =
[[NSString alloc] initWithContentsOfFile:scriptPath encoding:NSASCIIStringEncoding error:nil];
return [Utils runApplescript:scriptSource withVariables:variables];
}
+ (BOOL)stringIsEmptyOrWhite:(NSString *)string {
string = [string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
return [string isEqualToString:#""];
}
#end
Easier, yes, although whether that’s your actual problem is another question.
http://appscript.sourceforge.net/asoc.html
I assume you’ve already got other details, including sandboxing and hardening settings and plist entries, taken care of. (Recent Xcode upgrades also had a habit of breaking it when auto-upgrading your project files, by turning on hardening for you so Apple events can’t get out.)

UIActivityViewController not showing AirDrop option in iOS11

I have some code that shows an UIAcvityViewController for exporting a custom object from my app, formatted as JSON. This code works fine in previous versions of iOS but now fails it iOS 11. The problem is that when the Activity View Controller displays, it does not display the Airdrop panel or available recipient devices at all. The document is an NSDictionary that is encoded and written to a NSData object and then written to disk and referenced by an NSURL. As I stated, this code worked fine and still works fine in previous versions of iOS. I also have another place where I am using the UIActivityViewController to export an image file, and Airdrop continues to work fine in iOS 11. I am assuming that the issue has to do with the format of the file being exported and referenced by the URL I am referencing in the url key of the ActivityProvider, but I have tried every way of outputting and encoding this object that makes sense. Here is the code I am using:
NSString *textToShare = #"I am sharing this record with you!";
NSURL* url = [self.record exportNoteToURL];
NSMutableArray* activityProviders = [[NSMutableArray alloc]initWithCapacity:0];
NoteRecordActivityProvider *provider = [[NoteRecordActivityProvider alloc] initWithPlaceholderItem:#{#"body":textToShare, #"url":url}];
[activityProviders addObject:provider];
//Initialize the ActivityViewController
UIActivityViewController *activityController = [[UIActivityViewController alloc] initWithActivityItems:activityProviders applicationActivities:applicationActivities];
NSArray *excludeActivities = #[UIActivityTypePostToFacebook,
UIActivityTypePostToTwitter,
UIActivityTypePostToWeibo,
//UIActivityTypeMessage,
//UIActivityTypeMail,
//UIActivityTypePrint,
UIActivityTypeCopyToPasteboard,
UIActivityTypeAssignToContact,
UIActivityTypeSaveToCameraRoll,
UIActivityTypeAddToReadingList,
UIActivityTypePostToFlickr,
UIActivityTypePostToVimeo,
UIActivityTypePostToTencentWeibo,
//UIActivityTypeAirDrop,
UIActivityTypeOpenInIBooks];
activityController.excludedActivityTypes = excludeActivities;
[activityController setValue:[NSString stringWithFormat:#"Record: %#", self.record.title] forKey:#"subject"];
activityController.popoverPresentationController.barButtonItem = (UIBarButtonItem*)sender;
activityController.completionWithItemsHandler = ^(NSString *activityType, BOOL completed, NSArray *returnedItems, NSError *activityError) {
if (completed)
{
NSLog(#"The Activity: %# was completed", activityType);
}
else
{
NSLog(#"The Activity: %# was NOT completed", activityType);
}
[FileSystemProvider clearExportsDirectory];
};
[self presentViewController:activityController animated:YES completion:nil];
Following is the code that exports the dictionary to the URL, which seems to be working properly.
#pragma mark Document Export
-(NSURL*) exportNoteToURL
{
NSMutableDictionary* dict =[[NSMutableDictionary alloc]initWithCapacity:0];
// 1 Create Dictionary
NSDictionary* noteDict = [self getSettingsDictionary];
[dict setValue:noteDict forKey:#"Note"];
// 2 Get File Name and create file path
NSString* fullFilePath = [[FileSystemProvider documentPath]stringByAppendingPathComponent:#"Exports"];
NSLog(#"Export Path: %#", fullFilePath);
[MiscUtilities createDirectory:fullFilePath];
NSString* fileName = #"ExportedNoteRecord.lgz";
fileName = self.title;
fileName = [fileName stringByReplacingOccurrencesOfString:#" " withString:#""];
fileName = [MiscUtilities SanitizeFileNameString:fileName];
fileName = [NSString stringWithFormat:#"%#%#", fileName, #".lgz"];
fullFilePath = [fullFilePath stringByAppendingPathComponent:fileName];
// 3 Write dictionary to FileSystem
NSURL* url = [[NSURL alloc]initFileURLWithPath:fullFilePath];
BOOL res = [dict writeToURL:url error:&error];
if (res)
{
return url;
}
else
{
return nil;
}
}
The problem is that your activityViewControllerPlaceholderItem returns a Dictionary. The type you return here is used as an indication of what type of object you are vending. Airdrop doesn't want to receive a mysterious Dictionary so it doesn't respond. If what are vending is a file URL, you should have been returning a URL.

Writing to /tmp folder iPad

I´m writing certain values to a file. See Write Operations below.
This works fine when using iPad 6.1 Simulator.
When trying the same thing on my iPad it fails. I think it´s something with sandboxing. I haven´t found out yet which path is best on iOS Devices to write stuff for internal use.
Any ideas?
#pragma mark Write Operations to Tmp Folder
- (BOOL) psWriteFileWithName: (NSString*) fileName
withString:(NSString*) string {
NSString *fileName = #"artistNumber";
NSError * error = NULL;
NSString *filePath = [NSString stringWithFormat:#"/tmp/%#.txt",fileName];
[string writeToFile:filePath
atomically:YES
encoding: NSUTF8StringEncoding
error:&error];
return YES;
}
You cannot write to /tmp since this is outside of your app sandbox.
However your app also has a temp directory, which can be referenced with the NSTemporaryDirectory() function:
Which works like:
NSString *tempfilePath = [NSTemporaryDirectory() stringByAppendingPathComponent:filename];
Here is you method with the correct NSTemporaryDirectory() implementation, also edit some error handling:
#pragma mark Write Operations to Tmp Folder
- (BOOL) psWriteFileWithName: (NSString*) fileName
withString:(NSString*) string {
NSString *fileName = #"artistNumber";
NSError *error = nil;
NSString *filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:filename];
if (![string writeToFile:filePath atomically:YES encoding:NSUTF8StringEncoding error:&error] ) {
NSLog(#"Error writing file: %#", error);
return NO;
}
return YES;
}

Mac-to-bluetooth device file transfer, simple example?

I've spent two days googling and reading the Bluetooth programming guide while trying to piece together a small Mac app that will retrieve images from a drop folder and send any new files to a predetermined device over Bluetooth. There doesn't seem to be many good examples available.
I'm at the point where I'm able to spawn the Bluetooth Service Browser and select the device and its OBEX service, establishing a service and creating a connection, but then nothing more happens. Could anyone please point me in the direction of/show me a simple example that would work?
AppDelegate source code enclosed. Thanks for reading!
#import "AppDelegate.h"
#implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
IOBluetoothServiceBrowserController *browser = [IOBluetoothServiceBrowserController serviceBrowserController:0];
[browser runModal];
//IOBluetoothSDPServiceRecord
IOBluetoothSDPServiceRecord *result = [[browser getResults] objectAtIndex:0];
[self describe:result];
if ([[result.device.name substringToIndex:8] isEqualToString:#"Polaroid"]) {
printer = result.device;
serviceRecord = result;
[self testPrint];
}
else {
NSLog(#"%# is not a valid device", result.device.name);
}
}
- (void) testPrint {
currentFilePath = #"/Users/oyvind/Desktop/_DSC8797.jpg";
[self sendFile:currentFilePath];
}
- (void) sendFile:(NSString *)filePath {
IOBluetoothOBEXSession *obexSession = [[IOBluetoothOBEXSession alloc] initWithSDPServiceRecord:serviceRecord];
if( obexSession != nil )
{
NSLog(#"OBEX Session Established");
OBEXFileTransferServices *fst = [OBEXFileTransferServices withOBEXSession:obexSession];
OBEXDelegate *obxd = [[OBEXDelegate alloc] init];
[obxd setFile:filePath];
[fst setDelegate:obxd];
OBEXError cnctResult = [fst connectToObjectPushService];
if( cnctResult != kIOReturnSuccess ) {
NSLog(#"Error creating connection");
return;
}
else {
NSLog(#"OBEX Session Created. Sending file: %#", filePath);
[fst sendFile:filePath];
[printer openConnection];
}
}
else {
NSLog(#"Error creating OBEX session");
NSLog(#"Error sending file");
}
}
#end
OK; here's what ultimately became the core parts of the functionality. The application I made was a sort of print server for Polaroid instant printers that would only accept images over Object Push.
First, ensure watched folder exists.
/*
Looks for a directory named PolaroidWatchFolder in the user's desktop directory
and creates it if it does not exist.
*/
- (void) ensureWatchedFolderExists {
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *url = [NSURL URLWithString:#"PolaroidWatchFolder" relativeToURL:[[fileManager URLsForDirectory:NSDesktopDirectory inDomains:NSUserDomainMask] objectAtIndex:0]];
BOOL isDir;
if ([fileManager fileExistsAtPath:[url path] isDirectory:&isDir] && isDir) {
[self log:[NSString stringWithFormat:#"Watched folder exists at %#", [url absoluteURL]]];
watchFolderPath = url;
}
else {
NSError *theError = nil;
if (![fileManager createDirectoryAtURL:url withIntermediateDirectories:NO attributes:nil error:&theError]) {
[self log:[NSString stringWithFormat:#"Watched folder could not be created at %#", [url absoluteURL]]];
}
else {
watchFolderPath = url;
[self log:[NSString stringWithFormat:#"Watched folder created at %#", [url absoluteURL]]];
}
}
}
Then scan for available printers:
/*
Loops through all paired Bluetooth devices and retrieves OBEX Object Push service records
for each device who's name starts with "Polaroid".
*/
- (void) findPairedDevices {
NSArray *pairedDevices = [IOBluetoothDevice pairedDevices];
devicesTested = [NSMutableArray arrayWithCapacity:0];
for (IOBluetoothDevice *device in pairedDevices)
{
if ([self deviceQualifiesForAddOrRenew:device.name])
{
BluetoothPushDevice *pushDevice = [[BluetoothPushDevice new] initWithDevice:device];
if (pushDevice != nil)
{
[availableDevices addObject:pushDevice];
[pushDevice testConnection];
}
}
}
}
That last function call is to the BluetoothPushDevice's built-in method to test the connection. Here is the delegate handler for the response:
- (void) deviceStatusHandler: (NSNotification *)notification {
BluetoothPushDevice *device = [notification object];
NSString *status = [[notification userInfo] objectForKey:#"message"];
if ([devicesTested count] < [availableDevices count] && ![devicesTested containsObject:device.name]) {
[devicesTested addObject:device.name];
}
}
Upon server start, this method will run in response to a timer tick or manual scan:
- (void) checkWatchedFolder {
NSError *error = nil;
NSArray *properties = [NSArray arrayWithObjects: NSURLLocalizedNameKey, NSURLCreationDateKey, NSURLLocalizedTypeDescriptionKey, nil];
NSArray *files = [[NSFileManager defaultManager]
contentsOfDirectoryAtURL:watchFolderPath
includingPropertiesForKeys:properties
options:(NSDirectoryEnumerationSkipsHiddenFiles)
error:&error];
if (files == nil) {
[self log:#"Error reading watched folder"];
return;
}
if ([files count] > 0) {
int newFileCount = 0;
for (NSURL *url in files) {
if (![filesInTransit containsObject:[url path]]) {
NSLog(#"New file: %#", [url lastPathComponent]);
[self sendFile:[url path]];
newFileCount++;
}
}
}
}
When new files are found, ww first need to find a device that is not busy recieving a file of printing it:
/*
Loops through all discovered device service records and returns the a new OBEX session for
the first it finds that is not connected (meaning it is not currently in use, connections are
ad-hoc per print).
*/
- (BluetoothPushDevice*) getIdleDevice {
for (BluetoothPushDevice *device in availableDevices) {
if ([device.status isEqualToString:kBluetoothDeviceStatusReady]) {
return device;
}
}
return nil;
}
Then a file is sent with this method:
- (void) sendFile:(NSString *)filePath {
BluetoothPushDevice *device = [self getIdleDevice];
if( device != nil ) {
NSLog(#"%# is available", device.name);
if ([device sendFile:filePath]) {
[self log:[NSString stringWithFormat:#"Sending file: %#", filePath]];
[filesInTransit addObject:filePath];
}
else {
[self log:[NSString stringWithFormat:#"Error sending file: %#", filePath]];
}
}
else {
NSLog(#"No idle devices");
}
}
Upon transfer complete, this delegate method is called:
/*
Responds to BluetoothPushDevice's TransferComplete notification
*/
- (void) transferStatusHandler: (NSNotification *) notification {
NSString *status = [[notification userInfo] objectForKey:#"message"];
NSString *file = ((BluetoothPushDevice*)[notification object]).file;
if ([status isEqualToString:kBluetoothTransferStatusComplete]) {
if ([filesInTransit containsObject:file]) {
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *error = nil;
[fileManager removeItemAtPath:file error:&error];
if (error != nil) {
[self log:[NSString stringWithFormat:#"**ERROR** File %# could not be deleted (%#)", file, error.description]];
}
[self log:[NSString stringWithFormat:#"File deleted: %#", file]];
[filesInTransit removeObject:file];
}
else {
[self log:[NSString stringWithFormat:#"**ERROR** filesInTransit array does not contain file %#", file]];
}
}
[self updateDeviceStatusDisplay];
}
I hope this helps someone!

Can I access the files used for external binary storage in Core Data?

I am working on a media DB application. I have a custom model with data storage and think about rewriting it to Core Data. One use case that is of particular interest to me is movie storage. I store movie files in the DB, but the media framework can only read movies from files (not data).
Core Data offers a handy feature called “external binary storage”, where the entity data is not stored in the DB, but in an external file. This is transparent to the Core Data API user. My question is, can I get the path to the external file, so that I could store a movie using Core Data and then easily load it from its Core Data external file?
Yes, you CAN access the files stored in External Storage. It takes a bit of hacking, and may not be completely kosher with Apple's App Store, but you can do it fairly easily.
Assuming we have an NSManagedObject Subclass 'Media', with a 'data' property that has been set to 'Allows External Storage' in the Core Data Editor:
// Media.h
// Examples
//
// Created by Garrett Shearer on 11/21/12.
// Copyright (c) 2012 Garrett Shearer. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
#interface CRMMedia : NSManagedObject
#property (nonatomic, retain) NSString * name;
#property (nonatomic, retain) NSData * data;
#end
And a handy-dandy NSString category:
// NSString+Parse.m
// Examples
//
// Created by Garrett Shearer on 11/21/12.
// Copyright (c) 2012 Garrett Shearer. All rights reserved.
//
#import "NSString+Parse.h"
#implementation NSString (Parse)
- (NSString*)returnBetweenString:(NSString *)inString1
andString:(NSString *)inString2
{
NSRange substringRange = [self rangeBetweenString:inString1
andString:inString2];
logger(#"substringRange: (%d, %d)",substringRange.location,substringRange.length);
logger(#"string (self): %#",self);
return [self substringWithRange:substringRange];
}
/*
Return the range of a substring, searching between a starting and ending delimeters
Original Source: <http://cocoa.karelia.com/Foundation_Categories/NSString/Return_the_range_of.m>
(See copyright notice at <http://cocoa.karelia.com>)
*/
/*" Find a string between the two given strings with the default options; the delimeter strings are not included in the result.
"*/
- (NSRange) rangeBetweenString:(NSString *)inString1 andString:(NSString *)inString2
{
return [self rangeBetweenString:inString1 andString:inString2 options:0];
}
/*" Find a string between the two given strings with the given options inMask; the delimeter strings are not included in the result. The inMask parameter is the same as is passed to [NSString rangeOfString:options:range:].
"*/
- (NSRange) rangeBetweenString:(NSString *)inString1 andString:(NSString *)inString2
options:(unsigned)inMask
{
return [self rangeBetweenString:inString1 andString:inString2
options:inMask
range:NSMakeRange(0,[self length])];
}
/*" Find a string between the two given strings with the given options inMask and the given substring range inSearchRange; the delimeter strings are not included in the result. The inMask parameter is the same as is passed to [NSString rangeOfString:options:range:].
"*/
- (NSRange) rangeBetweenString:(NSString *)inString1 andString:(NSString *)inString2
options:(unsigned)inMask range:(NSRange)inSearchRange
{
NSRange result;
unsigned int foundLocation = inSearchRange.location; // if no start string, start here
NSRange stringEnd = NSMakeRange(NSMaxRange(inSearchRange),0); // if no end string, end here
NSRange endSearchRange;
if (nil != inString1)
{
// Find the range of the list start
NSRange stringStart = [self rangeOfString:inString1 options:inMask range:inSearchRange];
if (NSNotFound == stringStart.location)
{
return stringStart; // not found
}
foundLocation = NSMaxRange(stringStart);
}
endSearchRange = NSMakeRange( foundLocation, NSMaxRange(inSearchRange) - foundLocation );
if (nil != inString2)
{
stringEnd = [self rangeOfString:inString2 options:inMask range:endSearchRange];
if (NSNotFound == stringEnd.location)
{
return stringEnd; // not found
}
}
result = NSMakeRange( foundLocation, stringEnd.location - foundLocation );
return result;
}
#end
Now its time for some magic.... We are going to create a Category method that parses the filename from the [data description] string. When operating on an instance of the Media subclass, 'data' is actually an 'External Storage Reference', not an NSData object. The filename of the actual data is stored in the description string.
// Media+ExternalData.m
// Examples
//
// Created by Garrett Shearer on 11/21/12.
// Copyright (c) 2012 Garrett Shearer. All rights reserved.
//
#import "Media+ExternalData.h"
#import "NSString+Parse.h"
#implementation Media (ExternalData)
- (NSString*)filePathString
{
// Parse out the filename
NSString *description = [self.data description];
NSString *filename = [description returnBetweenString:#"path = " andString:#" ;"];
// Determine the name of the store
NSPersistentStoreCoordinator *psc = self.managedObjectContext.persistentStoreCoordinator;
NSPersistentStore *ps = [psc.persistentStores objectAtIndex:0];
NSURL *storeURL = [psc URLForPersistentStore:ps];
NSString *storeNameWithExt = [storeURL lastPathComponent];
NSString *storeName = [storeNameWithExt stringByDeletingPathExtension];
// Generate path to the 'external data' directory
NSString *documentsPath = [[[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory
inDomains:NSUserDomainMask] lastObject] path];
NSString *pathComponentToExternalStorage = [NSString stringWithFormat:#".%#_SUPPORT/_EXTERNAL_DATA",storeName];
NSString *pathToExternalStorage = [documentsPath stringByAppendingPathComponent:pathComponentToExternalStorage];
// Generate path to the media file
NSString *pathToMedia = [pathToExternalStorage stringByAppendingPathComponent:filename];
logger(#"pathToMedia: %#",pathToMedia);
return pathToMedia;
}
- (NSURL*)filePathUrl
{
NSURL *urlToMedia = [NSURL fileURLWithPath:[self filePathString]];
return urlToMedia;
}
#end
Now you have an NSString path and a NSURL path to the file. JOY!!!
Something to take note of, I have had issues loading movies with this method... but I also came up with a workaround. It appears that MPMoviePlayer will not access the files in this directory, so the solution was to temporarily copy the file to the documents directory, and play that. Then delete the temp copy when I unload my view:
- (void)viewDidLoad
{
[super viewDidLoad];
[self copyTmpFile];
}
- (void)viewDidUnload
{
logger(#"viewDidUnload");
[_moviePlayer stop];
[_moviePlayer.view removeFromSuperview];
[self cleanupTmpFile];
[super viewDidUnload];
}
- (NSString*)tmpFilePath
{
NSString *documentsPath = [[[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory
inDomains:NSUserDomainMask] lastObject] path];
NSString *tmpFilePath = [documentsPath stringByAppendingPathComponent:#"temp_video.m4v"];
return tmpFilePath;
}
- (void)copyTmpFile
{
NSString *tmpFilePath = [self tmpFilePath];
NSFileManager *mgr = [NSFileManager defaultManager];
NSError *err = nil;
if([mgr fileExistsAtPath:tmpFilePath])
{
[mgr removeItemAtPath:tmpFilePath error:nil];
}
[mgr copyItemAtPath:_media.filePathString toPath:tmpFilePath error:&err];
if(err)
{
logger(#"error: %#",err.description);
}
}
- (void)cleanupTmpFile
{
NSString *tmpFilePath = [self tmpFilePath];
NSFileManager *mgr = [NSFileManager defaultManager];
if([mgr fileExistsAtPath:tmpFilePath])
{
[mgr removeItemAtPath:tmpFilePath error:nil];
}
}
Good Luck!
If you want to access the data directly (i.e., not through CoreData), you may be better off giving each file a UUID as name, and store that name in the database, and store the actual file yourself.
If you use UIManagedDocument, you have several options. Using the above technique, you can store the files alongside the database, because UIManagedDocument is really a file package.
Alternatively, you can subclass from UIManagedDocument and override the methods that handle reading/writing the "extra" files. This will give you access to the files themselves. You can hook there to do whatever you want, including grabbing the actual URL to the file CoreData automatically creates.
- (id)additionalContentForURL:(NSURL *)absoluteURL error:(NSError **)error
- (BOOL)readAdditionalContentFromURL:(NSURL *)absoluteURL error:(NSError **)error
- (BOOL)writeAdditionalContent:(id)content toURL:(NSURL *)absoluteURL originalContentsURL:(NSURL *)absoluteOriginalContentsURL error:(NSError **)error