Finding a Resource Text File - objective-c

Just to begin, I've read Read text file in a folder and other troubleshooting things on here, and I've followed the advice previously given.
However, my program still can't find my .txt file.
I've checked the name, the bundle resources, and the file itself, and everything seems to be in order. My code runs fine, and I get my own error message when it can't find the file. No crashes or anything out of the ordinary.
I've also changed the Preferences/Locations/Build Location to "Place Build Products in locations specified by targets." which was an issue before.
Any additional help?
Thanks in advance!
#import "FileArrayControl.h"
#implementation FileArrayControl
#synthesize partArray, arrayContent;
- (NSArray *) setFileToArray {
NSString *stemFile = [[NSBundle mainBundle] pathForResource:#"stemList.txt" ofType:#"txt" inDirectory:#"stemIDv0"];
if (stemFile != nil) {
// ...stuff...
} else {
NSLog(#"Error, file not found!");
}
NSLog(#"%#",partArray);
return partArray;
}
#end

You're incorrectly using pathForResource:ofType:inDirectory:. This method should not include the extension in the first parameter. So it would be:
NSString *stemFile = [[NSBundle mainBundle] pathForResource:#"stemList" ofType:#"txt" inDirectory:#"stemIDv0"];

Related

While loop with NSFileManager directory enumerator not running

I am seeking help to understand why a tutorial I am following is not working for me. I am running macOS 12.3.1, Xcode 13.3.1. The project is in Objective-C and using XIB.
This is a view-based NSTableView, using a folder of PNGs stored on my SSD for the imageView and the stringByDeletingPathExtension as stringValue for the cell's text field. I filled my code with NSLog calls to try and catch what could have been going awry.
Most setup is happening in applicationDidFinishLaunching:, where I initialise an NSMutableArray for the table's content, an NSString for the file path, then set up the file manager and the directory enumerator with said path (note: all working up to here).
Now comes the loop to populate the table contents' mutable array. I cannot understand why said loop gets skipped entirely! Its condition is to set an NSString equal to the nextObject of the directory enumerator. I am sure the loop gets skipped because the NSLog call after the loop runs!
Here is the entire code of applicationDidFinishLaunching:, including my comments and logs (I have just replaced my account name with ):
-(void)applicationDidFinishLaunching:(NSNotification *)aNotification {
_tableContents = [[NSMutableArray alloc] init];
NSString *path = #"/Users/<myUsername>/Developer/Apple-Programming-YT/Cocoa Programming/Flags/PNG/40x30";
// MARK: Debug 1
NSLog(#"path found: %#", path); // the correct path gets printed, as expected
NSFileManager *fileManager = [NSFileManager defaultManager];
NSDirectoryEnumerator *directoryEnum = [fileManager enumeratorAtPath:path];
NSString *file;
// MARK: Debug 2
NSLog(#"Checking that file is empty: %#", file); // (null) gets printed, as expected
// MARK: Debug 3
if (file != directoryEnum.nextObject) {
NSLog(#"File cannot be assigned to the Directory Enumerator");
} else if (file == directoryEnum.nextObject) {
NSLog(#"File properly assigned. Proceed!"); // this gets printed! Is it correct?
} else {
NSLog(#"Something went wrong during assignment of nextObject to file");
}
while (file = [directoryEnum nextObject]) {
NSLog(#"While loop entered!"); // this doesn't get printed! Why?!
// MARK: Debug 4
NSLog(#"File: %#", file);
NSString *filePath = [path stringByAppendingFormat:#"/%#", file];
// MARK: Debug 5
NSLog(#"Image filepath: %#", filePath);
NSDictionary *obj = #{#"image": [[NSImage alloc] initByReferencingFile:filePath],
#"name": [file stringByDeletingPathExtension]};
[self.tableContents addObject:obj];
}
[self.tableView reloadData];
NSLog(#"Table View Reloaded"); // This gets printed!
}
I have uploaded the full app to GitHub, in case you may want to look at it and see if something else could be wrong, but every outlet, delegate, data source is connected.
Now for my diagnosis & ideas:
The Debug 3 mark is what I find most interesting. AFAIK file should still be (null), so how checking if it is equal to directoryEnum.nextObject returns YES?
I created Debug 3 because the NSLog checking whether the loop had been entered didn't get printed. I therefore assumed the condition for the while loop had a problem.
I then tried to create a do-while loop instead of this while loop and, of course, the code ran. For the log with "Image filepath" it returned the address above followed by (null), as if it didn't find the file. But how is it possible if the file is indeed there? Do I require some sort of permission to access it? Being the object empty, the next line in the console was quite clear: "attempt to insert nil object from objects[1]".
But now, how do I solve this?
Any help here is much appreciated. If you download it from GitHub, please replaces the *path string with a folder of PNGs on your SSD.
Thank you.
I don't think you can access the filesystem directly with a path like that any more. If you check the value of file in your code, it is nil, which means that file == directoryEnum.nextObject will evaluate to true.
You have to create a path starting with NSHomeDirectory() or similar and add components to it. This makes a path that goes via your application support folder, which contains an alias to the desktop. I'm not sure why that's OK and accessing it directly is not, but I'm not a Mac developer.
I'd have to say following a tutorial as old as that, you're going to struggle with a lot of things.

NSFileWrapper, lazy loading and saving

I have an NSDocument based application that uses filewrappers to save and load its data.
The document can have all kinds of resources, so I don't want to load everything into memory. I might be doing something fundamentally wrong, but as soon as I change one (inner) file and then save, I can't read any file that hasn't been loaded into memory.
I have separated the relevant code into a separate project to reproduce this behaviour, and I get the same results. The basic flow is this:
I load an existing document from disk. The main fileWrapper is a directory filewrapper (I'll call that main) containing two other filewrappers (sub1 and sub2). The two inner filewrappers are not loaded at this point.
When the user wants to edit sub1, it is loaded from disk.
The user saves the document
If the user wants to edit the other file (sub2), it cannot load. The error that appears:
-[NSFileWrapper regularFileContents] tried to read the file wrapper's contents lazily but an error occurred: The file couldn’t be opened because it doesn’t exist.
Here is the relevant code in my project:
This code might be easier to read in this gist:
https://gist.github.com/bob-codingdutchmen/6869871
#define FileName01 #"testfile1.txt"
#define FileName02 #"testfile2.txt"
/**
* Only called when initializing a NEW document
*/
-(id)initWithType:(NSString *)typeName error:(NSError *__autoreleasing *)outError {
self = [self init];
if (self) {
self.myWrapper = [[NSFileWrapper alloc] initDirectoryWithFileWrappers:nil];
NSLog(#"Initializing new document...");
NSString *testString1 = #"Lorem ipsum first sub file";
NSString *testString2 = #"This is the second sub file with completely unrelated contents";
NSFileWrapper *w1 = [[NSFileWrapper alloc] initRegularFileWithContents:[testString1 dataUsingEncoding:NSUTF8StringEncoding]];
NSFileWrapper *w2 = [[NSFileWrapper alloc] initRegularFileWithContents:[testString2 dataUsingEncoding:NSUTF8StringEncoding]];
w1.preferredFilename = FileName01;
w2.preferredFilename = FileName02;
[self.myWrapper addFileWrapper:w1];
[self.myWrapper addFileWrapper:w2];
}
return self;
}
-(NSFileWrapper *)fileWrapperOfType:(NSString *)typeName error:(NSError *__autoreleasing *)outError {
// This obviously wouldn't happen here normally, but it illustrates
// how the contents of the first file would be replaced
NSFileWrapper *w1 = [self.myWrapper.fileWrappers objectForKey:FileName01];
[self.myWrapper removeFileWrapper:w1];
NSFileWrapper *new1 = [[NSFileWrapper alloc] initRegularFileWithContents:[#"New file contents" dataUsingEncoding:NSUTF8StringEncoding]];
new1.preferredFilename = FileName01;
[self.myWrapper addFileWrapper:new1];
return self.myWrapper;
}
-(BOOL)readFromFileWrapper:(NSFileWrapper *)fileWrapper ofType:(NSString *)typeName error:(NSError *__autoreleasing *)outError {
self.myWrapper = fileWrapper;
return YES;
}
- (IBAction)button1Pressed:(id)sender {
// Read from file1 and show result in field1
NSFileWrapper *w1 = [[self.myWrapper fileWrappers] objectForKey:FileName01];
NSString *string1 = [[NSString alloc] initWithData:w1.regularFileContents encoding:NSUTF8StringEncoding];
[self.field1 setStringValue:string1];
}
- (IBAction)button2Pressed:(id)sender {
// Read from file2 and show result in field2
NSFileWrapper *w2 = [[self.myWrapper fileWrappers] objectForKey:FileName02];
NSString *string2 = [[NSString alloc] initWithData:w2.regularFileContents encoding:NSUTF8StringEncoding];
[self.field2 setStringValue:string2];
}
The bottom two methods are only for updating the UI so I can see what happens.
To change the contents of a file, I remove the existing fileWrapper and add a new one. This is the only way I've found to change the contents of a file, and the way I've seen it done in other SO answers.
When a document is loaded from disk, I keep the fileWrapper around so I can use it (called myWrapper in the code above)
The Apple docs say that NSFileWrapper supports lazy loading and incremental saving, so I'm assuming that my code has some fundamental flaw that I can't see.
An NSFileWrapper is essentially a wrapper around a unix file node. If the file is moved the wrapper stays valid.
The problem yo seem to have is that creating a new file wrapper during saving is a new folder. And the system deletes your previous wrapper including sub2.
To achieve what you want you need to change to incremental saving, i.e. Only saving changed parts in place. See "save in place" in NSDocument.
In your -fileWrapperOfType:error: method, try building a new file wrapper that has new contents for the changed members and references the old file wrappers for the unchanged members.
Following the documentation to addFileWrapper: you add a child (subdirectory) to it, means
directory/
addfileWrapper:fileName1
directory/fileName1/
addfileWrapper:fileName2
directory/fileName1/fileName2.
That file doesn't exist.
You have to use
addRegularFileWithContents:preferredFilename:
instead.

'unrecognized selector sent to instance'?

I'm new to Objective C and having trouble understanding why I am getting this error. I've checked other similar questions, but haven't been able to resolve the issue.
The error is "-[NSConcreteMutableData base64Decoded]: unrecognized selector sent to instance 0x6e15610"
Here is a snippet of the problem code, where the call to base64Decoded is causing the crash.
#import "DDData.h"
- (NSString *)decodeBase64:(NSString *)input
{
NSData* dataDecoded = [[input dataUsingEncoding:NSUTF8StringEncoding] base64Decoded];
return [NSString stringWithUTF8String:[dataDecoded bytes]];
}
And in DDData.h:
#import <Foundation/Foundation.h>
#interface NSData (DDData)
- (NSData *)base64Decoded;
#end
and DDData.m:
#implementation NSData (DDData)
- (NSData *)base64Decoded
{
// Excluding function code, as it never gets to here
}
#end
Just a note that the Project has ARC enabled. Any ideas as to what might be the issue here? Thanks.
EDIT: I have adjusted the above code to help debug the error:
NSData* dataDecoded = [input dataUsingEncoding:NSUTF8StringEncoding];
[dataDecoded base64Decoded];
dataDecoded gets a value from dataUsingEncoding, it is not nil when the call to base64Decoded is made. When I step over to the called to base64Decoded, it crashes.
Insert a break point in your code and step through it and you'll see exactly where it breaks.
You may also want to check that the DDData files are properly included in your project by looking at the target membership of those files, the .m should be ticked.
SimonH pointed out the solution correctly in one of the sub-comments. I was having the same problem with a custom method i defined in a NSData category. The solution better explained:
Make sure the .m file is included in the projects Build Phases->Compile Sources.
Right click on the .m file in the project navigator and click "Show file Inspector". Under File Inspector make sure you check the target you are building for otherwise it wont be included and the calling that method will crash.
You get that kind of message if you try to execute an undefined method on an object. Try it like this:
NSData *dataDecoded = [[input dataUsingEncoding:NSUTF8StringEncoding] base64EncodedString];
There is no base64Decoded method as far as I know, but there is base64EncodedString. So when you send the base64Decoded message to your NSData object, it isn't recognized because it's simply not there.
Follow below debugging steps to resolve it.
Put breakpoints in your code and check step by step where that is breaking.
Also, check if you have added DDData.m source file in your project target properly.
You should also check the object presence before using it. Check below sample code.
- (NSString *)decodeBase64:(NSString *)input {
if(input) {
NSData *utfData = [input dataUsingEncoding:NSUTF8StringEncoding];
if(utfDFata) {
NSData* dataDecoded = [utfDFata base64Decoded];
return [NSString stringWithUTF8String:[dataDecoded bytes]];
}
}

Why isn't [NSBundle mainBundle] working here?

I've never loaded a bundle, so I'm not sure why this is not working. I don't think it matters, but the .xib in question here is in the same Resources folder as all my other .xibs.
NSArray *array = [[NSBundle mainBundle] loadNibNamed:#"S3AsyncView" owner:self];
Returns this error:
Instance method -loadNibNamed:owner not found. Return type defaults to id
I find this error strange, because the return type of [NSBundle mainBundle] is of course NSBundle.
There is no such method in NSBundle, hence the error.
I guess you are looking for:
loadNibNamed:owner:options:
Documentation link
You can pass nil to the options, as it expect a NSDictionary
So in your case:
NSArray *array = [[NSBundle mainBundle] loadNibNamed:#"S3AsyncView" owner:self options:nil];
EDIT
If it still doesn't work, verify you have included <UIKit/UIKit.h>.
EDIT 2
Ok, now I see. You tagged your question with iOS, but now you say it's a Cocoa app.
The loadNibNamed:owner:options: is a UIKit addition, so available only on iPhone.
On Mac OS X, you'll use the + (BOOL)loadNibNamed:(NSString *)aNibName owner:(id)owner class method.
So:
NSArray *array = [ NSBundle loadNibNamed: #"whatever" owner: self ];
Three things:
Make sure that you're spelling the method name right. The error message you give shows the method name as: -loadNibNamed:owner:options, which isn't right. There should be a colon after the "options". Perhaps you missed that in pasting the name into your message, but the lesson here is to check carefully that you're using exactly the right method name, with no spelling errors, omitted parts, missing colons, etc.
Make sure that you're linking against UIKit. NSBundle is part of the Foundation framework, but the -loadNibNamed:owner:options: method comes from a UIKit Additions category on NSBundle that's part of UIKit. If you don't link against UIKit, then, NSBundle won't have that method.
I see that you've removed ios from your list of tags. If you're writing for Cocoa and trying to load a nib, see the NSNib class for some convenient methods for loading nibs.
I have come across the very same problem while fixing an issue in a low-level Cocoa/Objective-C++ framework. Strictly speaking, build issue came from this function:
bool osxNibLoadMenuNibFile()
{
const auto cvAppKitVersion = floor( NSAppKitVersionNumber );
if( cvAppKitVersion >= NSAppKitVersionNumber10_8 )
{
NSBundle * mainBundle = [NSBundle mainBundle];
NSDictionary * bundleInfoDict = [mainBundle infoDictionary];
if( bundleInfoDict != nil )
{
NSString * mainNibFleNameStr = [bundleInfoDict valueForKey:#"NSMainNibFile"];
if( mainNibFleNameStr != nil )
{
if( [mainBundle loadNibNamed:mainNibFleNameStr owner:[NSApplication sharedApplication] topLevelObjects:nil] )
{
return true;
}
}
}
}
return false;
}
Clang gave me:
warning: instance method '-loadNibNamed:owner:topLevelObjects:' not found (return type defaults to 'id') [-Wobjc-method-access]
The issue was not a build configuration, as all standard frameworks were there already. The issue was more trivial: the definition of that single method is present in a separate header. So please be sure to add:
#import <AppKit/NSNibLoading.h>
which contains:
#interface NSBundle(NSNibLoading)
- (BOOL)loadNibNamed:(NSNibName)nibName owner:(nullable id)owner topLevelObjects:(NSArray * _Nullable * _Nullable)topLevelObjects API_AVAILABLE(macos(10.8));
#end
Interestingly enough, CLion gaves me "unused import directive" even though I definitely use it. Hope this helps someone!

how to call a mothod defined in another class

Hi
I have a class named as root:
In root.h :-
#import "UIKit/UIKit.h"
#import "AVFoundation/AVFoundation.h"
#import "AudioToolbox/AudioToolbox.h"
#interface root : UIView {
}
+(void)somefunction:(BOOL) sf;
#end
in root.m the definition of somefunction is as follows
-(void)somefunction:(BOOL) sf {
//AVAudioPlayer *myExampleSound; //this variable can be named differently
if ( issoundon==TRUE) {
NSString *path =[[NSBundle mainBundle] pathForResource:"bg" ofType:#"wav"];
SystemSoundID soundID;
AudioServicesCreateSystemSoundID(
(CFURLRef) [NSURL fileURLWithPath:path], &soundID);
AudioServicesPlaySystemSound(soundID);
}
else{
NSString *path =[[NSBundle mainBundle] pathForResource:nil ofType:#"wav"];
SystemSoundID soundID;
AudioServicesCreateSystemSoundID(
(CFURLRef) [NSURL fileURLWithPath:path], &soundID);
AudioServicesPlaySystemSound(soundID);
}
now i have imported root.h in another class and i am calling the "somefunction" as follows
bool abc=true;
[root somefunction:true];
but at this point my app terminates(crashes).
basically i am trying to set background music to my app (as the game starts) and in the middle of the game i allow user to switch of the sound.(it is crashing even i am calling the function in delegate of the view.)
please tell me what is happening wrong coz my code is compiling properly(with a few warning though).
Sure, your code compiles successfully. But you know what? There is a typo in your code which is causing your program to crash. Never assume your code has no typos just because it compiles successfully.
In your root.m file you have this:
-(void)somefunction:(BOOL) sf {
It should be a +, not a -, like in your header file:
+(void)somefunction:(BOOL) sf {
There might be a discrepancy between the C type bool and the Objective-C type BOOL too, but I'm not too sure about that:
bool abc=true; // Shouldn't this be BOOL abc = YES, and
[root somefunction:true]; // shouldn't you be passing abc here?
Your code is incomplete, and without context it's really hard to say what the bug might be. However:
You haven't defined issoundon anywhere
where have you constructed your root object? in interface builder?
why is it a subclass of UIView?
is AudioServicesPlaySystemSound synchronous?
But mainly, I think your problem is here:
why would you expect to be able to play a file called (nil).wav?
I suggest you provide a crash dump and error log to assist people in answering this question.
See this SO question: playing background audio on iphone
"but at this point my app terminates(crashes)"
We need to know more about that to help you with this.
In the Xcode console, it should be putting out messages that would narrow in on what the problem is. A stack trace would be helpful too.
My total guess is that your local instance of root is undefined and so you're calling an unrecognized selector on that object. But without more help from the console, that's a complete guess.