NSView Drag Operation Error - macos

I'm coding in Cocoa, on OS X.
I'm trying to receive a file that is dragged and dropped onto my NSView subclass - which I can do; and get its contents and filename and display them - which I can do for any type of file first time, but on the second time, when I try and drag another file on, I can only set the title setTitle:, but not the main body text setText:
The errors I am getting are:
Canceling drag because exception 'NSInternalInconsistencyException' (reason 'Invalid parameter not satisfying: aString != nil') was raised during a dragging session
and
Assertion failure in -[NSTextFieldCell _objectValue:forString:errorDescription:], /SourceCache/AppKit/AppKit-1187/AppKit.subproj/NSCell.m:1532
My code (sorry, it's quite long!):
- (BOOL)performDragOperation:(id<NSDraggingInfo>)sender {
NSPasteboard *pboard;
NSDragOperation sourceDragMask;
sourceDragMask = [sender draggingSourceOperationMask];
pboard = [sender draggingPasteboard];
if ([[pboard types] containsObject:NSFilenamesPboardType]) {
NSURL *file = [NSURL URLFromPasteboard:pboard];
//NSData *data = [NSData dataWithContentsOfURL:file];
NSError *error;
NSStringEncoding encoding;
NSString *contentString = [[NSString alloc] initWithContentsOfURL:file usedEncoding:&encoding error:&error];
NSLog(#"Error: %#",error);
NSString *last = [[file path] lastPathComponent];
NSArray *parts = [last componentsSeparatedByString:#"."];
NSString *filename = [parts objectAtIndex:0];
NSString *fileType = [parts objectAtIndex:1];
NSLog(#"FILETYPE: %#", fileType);
if ([fileType isEqualToString:#"txt"] || [fileType isEqualToString:#"md"]) {
[self setTitle:filename];
if (self.textViewString == (id)[NSNull null] || self.textViewString.length == 0) {
[self setText:contentString];
} else {
BOOL whatToDo = (NSRunCriticalAlertPanel(#"What do you want to do?", nil, #"Append", #"Replace", nil) == NSAlertDefaultReturn);
if (whatToDo) {
//Append
[self setText:[NSString stringWithFormat:#"%#\n%#",self.textViewString,contentString]];
} else {
//Replace
[self setText:contentString];
}
}
return YES;
} else {
return NO;
}
} else if ([[pboard types] containsObject:NSPasteboardTypeString]) {
NSString *draggedString = [pboard stringForType:NSPasteboardTypeString];
if (self.textViewString == (id)[NSNull null] || self.textViewString.length == 0) {
[self setText:draggedString];
} else {
[self setText:[NSString stringWithFormat:#"%#\n%#",self.textViewString,draggedString]];
}
return YES;
}
else {
return NO;
}
}
Thanks in advance! :)

Sounds like Cocoa is canceling the drag when any exception is raised, and an exception is getting raised when something internally is expecting a string and is getting a nil value instead.
It's just a guess without having more information, but I would predict that stringWithFormat: is raising the exception, as it looks like the only really potentially fragile bit of what you have written.
You are doing a couple ill-advised things. First, you are assuming that -initWithContentsOfURL:usedEncoding:error: is succeeding. You should not do this. Instead, you need to pass an NSError ** that can be filled in on error, test whether contentString is nil, and, if so, check the error accordingly. I have a feeling that you will find you are getting nil, and the error will explain why.
Possibly unrelated, but your if (whatToDo) is not doing what you probably think it is. Since whatToDo is a pointer to an autoreleased NSNumber instance your conditional will always evaluate to true, since the pointer is non-zero. What you likely meant to do was something like the following:
BOOL whatToDo = (NSRunCriticalAlertPanel(#"What do you want to do?", nil, #"Append", #"Replace", nil) == NSAlertDefaultReturn);
if (whatToDo) {
//Append
[self setText:[NSString stringWithFormat:#"%#\n%#",self.textViewString,contentString]];
} else {
//Replace
[self setText:contentString];
}

Lots of thanks for the many tips and pieces of advice from this answer by Conrad Shultz! I've followed the advice and tips there.
However, my problem turned out to be very, very basic. It lied in the line BOOL whatToDo = (NSRunCriticalAlertPanel(#"What do you want to do?", nil, #"Append", #"Replace", nil) == NSAlertDefaultReturn);
It turns out a string must be passed to the second parameter, but I was passing nil. Fixed!

Related

non-persistence of object written to documentsDirectory - is

-- a question about how to make an object that is saved to the documents directory persist on the drive and be recoverable after the iDevice is rebooted.
Here's my problem. I make a data object with NSCoding and fill it with data. I write it to the documentsDirectory each time the data in the object are updated. I stop the app and start the app again, and my data object persists, with all of its data. But if I reboot the iPhone the code I wrote to recover and read the data object fails.
The code I wrote originally used only a NSString for the file path. It worked well under ios7 but it fails under ios8.
Reading up on things, I found this clue from the Apple documentation:
"Important: Although they are safe to use while your app is running, file reference URLs are not safe to store and reuse between launches of your app because a file’s ID may change if the system is rebooted. If you want to store the location of a file persistently between launches of your app, create a bookmark as described in Locating Files Using Bookmarks."
So I rewrote my ios7 file open and file close methods so they no longer use strings or urls but get their strings and urls from a bookmark that is saved using NSUserDefaults. Same problem: everything works fine so long as I do not power off the phone, but all is lost once I do. I am not able to solve this.
Here is my current series of steps. First I either determine (or if it already exists in NSUsrDefaults, I recover) the absolute path to the documentsDirectory, using a bookmark:
+ (NSString*) getGeoModelAbsolutePath
{
NSString *path;
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
NSURL *documentsDirectoryBookmarkURL;
NSData* documentsDirectoryBookmark = [userDefaults objectForKey:#"documentDirectoryBookmark"];
if(documentsDirectoryBookmark == nil)
{
documentsDirectoryBookmarkURL = [self getDocumentsDirectoryURL];
documentsDirectoryBookmark = [self bookmarkForURL:documentsDirectoryBookmarkURL];
}
documentsDirectoryBookmarkURL = [self urlForBookmark:documentsDirectoryBookmark];
path = documentsDirectoryBookmarkURL.path;
path = [path stringByAppendingString:#"/Model.mod"];
return path;
}
using methods modified from my ios7 code (which used only the getDocumentsDirectory method):
+ (NSString *)getDocumentsDirectory
{
NSURL *directory = [self getDocumentsDirectoryURL];
NSString * documentsDirectory = directory.path;
return documentsDirectory;
}
And
+ (NSURL *)getDocumentsDirectoryURL
{
NSURL *directory = [[[NSFileManager defaultManager]
URLsForDirectory:NSDocumentDirectory
inDomains:NSUserDomainMask]
lastObject];
return directory;
}
And
+ (NSData*)bookmarkForURL:(NSURL*)url {
NSError* theError = nil;
NSData* bookmark = [url bookmarkDataWithOptions:NSURLBookmarkCreationSuitableForBookmarkFile
includingResourceValuesForKeys:nil
relativeToURL:nil
error:&theError];
if (theError || (bookmark == nil)) {
// Handle any errors.
return nil;
}
return bookmark;
}
So now I have a NSString path with the model filename that I can use to get to the GeoModel
- (GeoModel*) openGeoModel
{
GeoModel *geoModel;
NSString* documentsDirectoryGeoModel =[FileManager getGeoModelAbsolutePath];
if([FileManager fileExistsAtAbsolutePath:documentsDirectoryGeoModel])
{
NSData* data = [NSData dataWithContentsOfFile: documentsDirectoryGeoModel]; //]documentsDirectoryGeoModel];
geoModel = [NSKeyedUnarchiver unarchiveObjectWithData: data];
NSString *unarchivedGeoModelVersion = geoModel.geoModel_VersionID;
if(![unarchivedGeoModelVersion isEqual: currentGeoModelVersion])
{
[FileManager deleteFile:documentsDirectoryGeoModel];
geoModel = [GeoModel geoModelInit];
[Utilities setGeoProjectCounter:0];
}
}
else
{
geoModel = [GeoModel geoModelInit];
}
[FileManager saveGeoModel];
return geoModel;
}
Which I then can save to the documentsDirectory as follows:
+ (BOOL)saveGeoModel
{
NSError *error = nil;
NSString *path = [self getGeoModelAbsolutePath];
[NSKeyedArchiver archiveRootObject:appDelegate.currentGeoModel toFile:path];
NSData* encodedData = [NSKeyedArchiver archivedDataWithRootObject: appDelegate.currentGeoModel];
BOOL success = [encodedData writeToFile: path options:NSDataWritingAtomic error:&error];
return success;
}
Which is always successful -- but is persistent only if I do not turn off the device! I am not making any progress with this: Any help would be much appreciated!
Thanks in advance
Tim Redfield
There. I think it is answered -- unless someone else has a comment on how to improve the above listings, they DO work as they ought to!

How can I wait for result from geocodeAddressString iPhone

I know its something to do with locks or dispatch groups, but I just cant seem to code it...
I need to know if the address was a valid address before leaving the method. Currently the thread just overruns and returns TRUE. I've tried locks, dispatchers the works but can't seem to get it correct. Any help appreciated:
- (BOOL) checkAddressIsReal
{
__block BOOL result = TRUE;
// Lets Build the address
NSString *location = [NSString stringWithFormat:#" %# %#, %#, %#, %#", streetNumberText.text, streetNameText.text, townNameText.text, cityNameText.text, countryNameText.text];
// Put a pin on it if it is valid
CLGeocoder *geocoder = [[CLGeocoder alloc] init];
[geocoder geocodeAddressString:location
completionHandler:^(NSArray* placemarks, NSError* error) {
result = [placemarks count] != 0;
}];
return result;
}
The documentation says that CLGeocoder calls the completionHandler on the main thread. Since you are probably also calling your method from the main thread it cannot wait for the geocoder's answer without giving it the opportunity to deliver the result.
That would be done by polling the runloop, using some API as -[NSRunLoop runMode:beforeDate:].
The disadvantage is that depending on the mode this will also deliver events and fire timers while waiting for the result.
Just use block as parameter:
- (void) checkAddressIsRealWithComplectionHandler:(void (^)(BOOL result))complectionHandler
{
__block BOOL result = TRUE;
// Lets Build the address
NSString *location = [NSString stringWithFormat:#" %# %#, %#, %#, %#", streetNumberText.text, streetNameText.text, townNameText.text, cityNameText.text, countryNameText.text];
// Put a pin on it if it is valid
CLGeocoder *geocoder = [[CLGeocoder alloc] init];
[geocoder geocodeAddressString:location
completionHandler:^(NSArray* placemarks, NSError* error) {
result = [placemarks count] != 0;
complectionHandler(result);
}];
}

Cannot find error with SQLITE statement call

I am having a problem locating my error. The code gets through the ID(init) okay and I know the database is loading. My issue is with the IF(sqlite3_prepare_v2...) statement. When I run the code this line does not ==SQLITE_OK and it jumps to the end. ANy help is appreciated. Thanks.
#implementation CityState
static CityState *WDdatabase;
+(CityState *)WDdatabase {
if(WDdatabase == nil) {
WDdatabase = [[CityState alloc] init];
}
return WDdatabase;
}
- (id)init {
self = [super init];
if (self) {
NSString *sqliteDB = [[NSBundle mainBundle] pathForResource:#"CityAndState" ofType:#"sqlite"];
if(sqlite3_open([sqliteDB UTF8String], &WDdatebase) != SQLITE_OK){
NSLog(#"Failed to open database");
}
}
return self;
}
-(NSArray *)getAllCities {
NSMutableArray *returnArray = [[NSMutableArray alloc] init];
NSString *query = #"SELECT * FROM Test_Table";
sqlite3_stmt *statement;
if(sqlite3_prepare_v2(WDdatebase, [query UTF8String], -1, &statement, NULL) == SQLITE_OK) {
while (sqlite3_step(statement) == SQLITE_ROW) {
}
sqlite3_finalize(statement);
}
return returnArray;
}
As far as I know, Objective-C does not do static class variables as you are attempting, and the symbol WDatabase (I'm assuming the typo is just here on SO, not in your code) is actually a class identifier instead of your static variable. To implement a singleton, you will need a file-scope variable (outside of the #implementation block) (and I recommend you name it starting with an underscore to clarify when you are accessing that and when you are accessing the class method. So basically, the call you are failing is likely because you are passing a class object instead of your var. Once you have setup the static var properly.
The proper syntax then for accessing a "class variable" (which end up being just a file-scope/statically-stored variable, by virtue of Objective-C's C soul), is to send a message to the class object:
sqlite3_prepare_v2([CityState wDdatebase], [query UTF8String], ...
Further, and just to be pedantic; if you are not using any of NSString's features and only need UTF8 (or "C-style") strings, you can use them just as you would in C:
-(NSArray *)getAllCities {
NSMutableArray *returnArray = [[NSMutableArray alloc] init];
char query[] = "SELECT * FROM Test_Table";
sqlite3_stmt *statement;
if(sqlite3_prepare_v2([CityState wDdatebase], query, -1, &statement, NULL) == SQLITE_OK) {
while (sqlite3_step(statement) == SQLITE_ROW) {
}
sqlite3_finalize(statement);
}
return returnArray;
}

Memory leaks in ipod - 100% leak while using instruments and iPod

-(NSString*)readTextColumnWithIndex:(int)index {
char* val = (char *)sqlite3_column_text(statement,index);
if (val==NULL) {
return nil;
}
return [NSString stringWithUTF8String:val];
}
I have 100% leak at return [NSString stringWithUTF8String:val];. Not sure how to fix them. I believe only alloc, retain etc could end up in memory leaks. Any help is greatly appreciated.
[NSString stringWithUTF8String:val] is the statement that created the object that is leaking - you need to see what happens to it next.
Are you retaining the NSString returned by readTextColumnWithIndex: somewhere?
-(NSDictionary*)loadDataRow:(SqlQuery*)q {
return [NSDictionary dictionaryWithObjectsAndKeys: [NSString stringWithFormat:#"%d",[q readIntColumn]],
#"TOCID",[q readTextColumn],#"SubChapter_Name",[q readTextColumn],
#"HtmlFileName",[q readTextColumn],#"ImageName",[q readTextColumn],
#"RefsTitleSection",[NSString stringWithFormat:#"%d",
[q readIntColumn]],#"PageId",
nil];
}
This is where it is called. readIntColumn in turn calls readTextColumnWithIndex and NSDictionary is returned back to an the caller method
chap_Arr = nil;
while([query step]) {
ChapName = [query readTextColumn];
chap_Arr = [mData objectForKey:ChapName];
if (!chap_Arr) {
chap_Arr = [[[NSMutableArray alloc] init] autorelease];
[mData setObject:chap_Arr forKey:ChapName];
}
[chap_Arr addObject:[self loadDataRow:query]];
}
I am not sure how i can release the chap_Arr as it is in a loop. I do have an RELEASE_SAFELY in dealloc.

Can plists be securely read from untrusted source?

I need simple client-server communication with iPhone app, and XML property lists seem like quite easy and simple solution that saves me trouble of dealing with XML parser delegates and building these structures myself.
I just wonder is it wise to use NSPropertyListSerialization class with external data? Are there any obscure plist features that could be exploited?
Yes, it's safe to use NSPropertyListSerialization with untrusted data, but after you turn the bag of bytes into a hiearchy of plist types, you have to validate those types to makes sure they match your expected data format.
For example, if you expect a dictionary with string keys, and NSNumbers as values, you have to validate that with something like:
NSString *errorDescription = nil;
NSPropertyListFormat format = 0;
id topObject = [NSPropertyListSerialization propertyListFromData:plistData mutabilityOption:NSPropertyListImmutable format:&format errorDescription:&errorDescription];
NSDictionary *validDictionary = nil;
if ([topObject isKindOfClass:[NSDictionary class]]) {
BOOL allNumbers = YES;
for(id value in [topObject allValues]) {
allNumbers = allNumbers && [value isKindOfClass:[NSNumber class]];
}
if (allNumbers) {
validDictionary = topObject;
}
}
return validDictionary;
If you don't do that, the source of the data could have placed plist values into the archive with mis matched types, or illegal values that could cause your client to misbehave and wind up being a security vulnerability.
#Jon Hess: thanks. I've created NSDictionary category to cut down on required number of isKindOfClasses.
#interface NSDictionary (TypedDictionary)
-(NSArray *)arrayForKey:(NSString*)key;
-(NSString *)stringForKey:(NSString*)key;
-(NSDictionary *)dictionaryForKey:(NSString*)key;
#end
#implementation NSDictionary (TypedDictionary)
-(NSArray *)arrayForKey:(NSString*)key {
NSArray *obj = [self objectForKey:key];
return [obj isKindOfClass:[NSArray class]] ? obj : nil;
}
-(NSString *)stringForKey:(NSString*)key {
NSString *obj = [self objectForKey:key];
return [obj isKindOfClass:[NSString class]] ? obj : nil;
}
-(NSDictionary *)dictionaryForKey:(NSString*)key {
NSDictionary *obj = [self objectForKey:key];
return [obj isKindOfClass:[NSDictionary class]] ? obj : nil;
}
#end

Resources