Drag and drop files into NSOutlineView - cocoa

I'm trying to implement simple drag and drop operation into NSOutlineView Based on Apple's example - https://developer.apple.com/library/mac/samplecode/SourceView/Introduction/Intro.html
All seems to be ok, but finally when I drop some files from Finder I get error:
[<ChildNode 0x60800005a280> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key description.') was raised during a dragging session
Here is my test project: https://www.dropbox.com/s/1mgcg2dysvs292u/SimpleDrag.zip?dl=0
What I exactly need in my app: allow user to drag and drop multiple files and folder into some tree list and then display them to user. Also save all this this into some file, so it can be loaded again with all user dragged files and folders.
A final result I want to have like this:

The description property of NSObject is read-only, and is generally set by providing a getter in the implementation file:
- (NSString *)description {
return [self urlString]; // Using urlString solely for demo purposes.
}
You can't set it, either via key-value coding or by direct assignment:
self.description = [self urlString]; // Xcode error: 'Assignment to readonly property'
[self setValue:[self urlString] forKey:#"description"];
In -[ChildNode copyWithZone:] an attempt is made to do the latter of the two, and that's what causes the warning to be logged to the console.
// -------------------------------------------------------------------------------
// copyWithZone:zone
// -------------------------------------------------------------------------------
- (id)copyWithZone:(NSZone *)zone
{
id newNode = [[[self class] allocWithZone:zone] init];
// One of the keys in mutableKeys is 'description'...
// ...but it's readonly! (it's defined in the NSObject protocol)
for (NSString *key in [self mutableKeys])
{
[newNode setValue:[self valueForKey:key] forKey:key];
}
return newNode;
}
This begs the question why do you get the warning in your app, and not in the sample app? From what I can tell no ChildNode instance is ever sent a copyWithZone: message in the sample app, whereas this does happen in your app, immediately after the drop. Of course there's a second question here as well: why do Apple explicitly include the description key-path when it can't be set this way? - unfortunately I can't help you with that.
A really handy way of trying to trap errors that don't actually cause exceptions is to add an All Exceptions breakpoint. If you do this in your sample app you'll see that the app freezes at the line that's causing the problem, giving you a better chance of figuring out the issue.

Related

Is it possible to use bindings in the nib with ScreenSaverDefaults?

I'm working on a screensaver, so I'm supposed to use ScreenSaverDefaults instead of NSUserDefaults. I'd like to have my configure panel use bindings for its UI, but they need to be wired to ScreenSaverDefaults, and I can't see a way to do that; the only defaults controller available in IB (xCode) is the standard user defaults controller. Is there a workaround, or is it just not possible to use bindings in the nib in the context of a screensaver?
I have successfully used bindings in a screen saver.
(Possibly important note: I'm still developing on 10.6, with XC 3.2.6; this code has only been lightly tested so far on 10.10 (but does seem to work). Also, I mix C++ and Objective-C, so you may have minor code cleanup to do, in what follows.)
My code creates, and the UI binds to, a custom user defaults controller (controls bind to File's Owner, which is set to my screen saver view, the controller key is empty, and the model key path is self.defaultsController.values.key_val, where key_val is whatever key is used in the defaults plist to access the value bound to a control).
You must also create your own screen saver defaults, and direct your custom user defaults controller to use them, early in screen saver initialization (such as in the initWithFrame: method of your screen saver view), like so:
// Find our bundle:
NSBundle * screenSaverBundle = [NSBundle bundleForClass: [self class]];
// Load per-user screen saver defaults:
ScreenSaverDefaults * screenSaverDefaults = [ScreenSaverDefaults defaultsForModuleWithName: [screenSaverBundle bundleIdentifier]];
// Create default defaults (values to use when no other values have been established):
NSDictionary * defaultDefaultValues = [self defaultDefaults];
// Register default defaults as fallback values (in registration domain):
[screenSaverDefaults registerDefaults: defaultDefaultValues];
// Configure custom user defaults controller to use the correct defaults:
// NOTE: defaultsController of NSUserDefaultsController * type *MUST* be declared using #property in your header, and accessors must be provided (typically using #synthesize in the implementation section).
defaultsController = [[NSUserDefaultsController alloc] initWithDefaults: screenSaverDefaults initialValues: nil]; // <- change nil to factory defaults, if desired
// Make sure changes are only saved when committed by user:
[defaultsController setAppliesImmediately: false];
(The above code was slightly rearranged from various methods in my own code; might have a typo or two, but the gist is correct.)
An implementation of defaultDefaults looks something like this (pardon my unconventional style):
- (NSDictionary *) defaultDefaults
{
NSString * ResourcesPath = [[self screenSaverBundle] resourcePath];
NSString * DefaultDefaultsPath = [ResourcesPath stringByAppendingPathComponent: #"DefaultDefaults.plist"];
NSDictionary * DefaultDefaults = [NSDictionary dictionaryWithContentsOfFile: DefaultDefaultsPath];
return DefaultDefaults;
}
If you want to provide a "factory reset" capability in your screen saver, you'll need to set the initialValues: argument to an "original factory values" dictionary when creating the custom user defaults controller. In response to a "Reset to Factory Defaults" button, simply send revertToInitialValues: to the controller.
Finally, please note that depending on the intended lifetime of some of the objects created in the above code, you may need to retain some of them (and release them properly later). I assume you understand this bhaller; I'm just pointing this out for the general audience.
Swift Version:
First, register default datas.
let defaults = ScreenSaverDefaults.init(forModuleWithName: "com.your.xxxx" ?? "")!
defaults.register(defaults: ["isShowSecond":true,
"userThemeIndex":0,
"userClockIndex":0
])
You can set value:
defaults.set(false, forKey: "isShowSecond")
You can get value:
let defaults = ScreenSaverDefaults.init(forModuleWithName: "com.your.xxxx" ?? "")!
let userClockIndex = defaults.bool(forKey: "isShowSecond")

What issues could arise when using GCD dispatchAfter() in this use case

I'm going through a book on OS X programing as a refresher and have a document app set up with an array controller, tableView etc. The chapter calls for implementing undo support by hand using NSInvocation. In the chapter, they call for adding a create employee method and manually, adding outlets to the NSArrayController, and connecting my add button to the new method instead of the array controller.
Instead I did this with my method for inserting new objects:
-(void)insertObject:(Person *)object inEmployeesAtIndex:(NSUInteger)index {
NSUndoManager* undoManager = [self undoManager];
[[undoManager prepareWithInvocationTarget:self]removeObjectFromEmployeesAtIndex:index];
if (![undoManager isUndoing]) {
[undoManager setActionName:#"Add Person"];
}
[self startObservingPerson:object];
[[self employees]insertObject:object atIndex:index];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// Wait then start editing
[[self tableView]editColumn:0 row:index withEvent:nil select:YES];
});
}
This works ok (looks a bit silly), but I was wondering the what issues could arise from this. I've done this elsewhere in order to execute code after an animation finished (couldn't figure out a better way).
Thanks in advance.
Why are you delaying the invocation of -editColumn:row:withEvent:select:?
Anyway, the risks are that something else will be done between the end of this -insertObject:... method and when the dispatched task executes. Perhaps something that will change the contents of the table view such that index no longer refers to the just-added employee.

Xcode Bad Access Error when calling method

I have a ViewController and in the viewDidLoad Method i initialize a GLKView.
After the GLKView is initialized i try to call a method of that View:
- (void)viewDidLoad
{
[super viewDidLoad];
self.myController = [[MyController alloc] init];
self.myController.parameter1 = #"BLA";
[self.myController initTargets];
}
My initTargets Method is not really amazing. It actually does not more
than this:
- (void) initTargets
{
MyTarget *targetOne = [[MyTarget alloc] initWithParameter:self.parameter1];
[self.targets addObject:targetOne];
}
When i try to call the initTargets Method like described above i get the
following error:
Thread 1: EXC_BAD_ACCESS (code=1, address = 0xe80000000)
I never had such a Problem with just calling methods and i dont know what
this error is trying to say me.
Go to Breakpoints pane in xCode, and click the plus sign from the lower left corner. By default, xCode adds an "All exceptions" breakpoint, which means it will stop at any exception, before the actual crash. This is how I debug all "Bad access" exceptions, except for the ones in blocks.
Your accessing an object or pointer that doesn't exist / hasn't been allocated yet. Or has already been destroyed.
So either MyTarget isn't being allocated properly or self.targets isn't.
What is self.targets ? Does that need to be allocated first ?
Also, you have self.paramter1, shouldnt it be self.myController.parameter1 ?

Overriding "Edited" in window title for NSDocument

How do I prevent a window title from displaying "Edited" for an NSDocument which is dirty?
I'm managing saving and autosaving myself, using a web service, and just don't want the distraction in the title bar.
I've tried overriding:
NSDocument's -isDocumentEdited and -hasUnautosavedChanges always to return NO.
-[NSWindowController setDocumentEdited] to do nothing, or always to use NO regardless of the parameter's actual value.
-[NSWindowController synchronizeWindowTitleWithDocumentName] to do nothing.
-[NSWindow setDocumentEdited] to do nothing, or always to use NO regardless of the parameter's actual value.
In all cases, the title bar still changes to Edited when I make changes to a saved document.
If I override -[NSDocument updateChangeCount:] and -[NSDocument updateChangeCountWithToken:forSaveOperation:] to do nothing, I can prevent this from happening, but it affects saving, autosaving, and other document behaviors, too.
I also tried this:
[[self.window standardWindowButton: NSWindowDocumentVersionsButton] setTitle:nil];
That displayed a blank string instead of Edited, but the dash still appeared – the one which normally separates the document name and Edited.
Any idea how to pry apart this part of the window from the document?
Several options:
To get a pointer to the "dash", look for a TextField in [window.contentView.superview.subviews] with a stringValue equals to "-". You can set its text to an empty string as well.
#implementation NSWindow (DashRetrivalMethod)
- (NSTextField*)versionsDashTextField
{
NSTextField* res = nil;
NSView* themeFrame = [self.contentView superview];
for (NSView* tmp in [themeFrame subviews])
{
if ([tmp isKindOfClass:[NSTextField class]])
{
if ([[(NSTextField*)tmp stringValue] isEqualToString:#"—"])
{
res = (NSTextField*)tmp;
break;
}
}
}
return res;
}
#end
You can override NSWindow's -setRepresentedURL:. This would also affect the NSWindowDocumentIconButton and the popup menu, but you can manually create it if you want by: [NSWindow standardWindowButton: NSWindowDocumentIconButton].
Override one of these three NSDocument's undocumented methods:
// Always return here NO if you don't want the version button to appear.
// This seems to be the cleanest options, besides the fact that you are
/// overriding a private method.
- (BOOL)_shouldShowAutosaveButtonForWindow:(NSWindow*)window;
// Call super with NO
- (void)_setShowAutosaveButton:(BOOL)flag;
// Here the button and the dash are actually created
- (void)_endVersionsButtonUpdates;
// Here Cocoa hide or unhide the edited button
- (void)_updateDocumentEditedAndAnimate:(BOOL)flag
Have you tried overriding NSDocuments - (BOOL)hasUnautosavedChanges in addition to overriding - (BOOL) isDocumentEdited?
Although this is a late answer, you can easily determine what is going to be the title of your NSDocument window by overriding
- (NSString *)windowTitleForDocumentDisplayName:(NSString *)displayName
in your NSWindowController and return the appropriate title.
You can do that also by overriding the property of your NSDocument:
- (NSString *)displayName
but this is not recommended by Apple, because that is normally used by the OS error handlers.
I added this answer, because none of the other answers really set me on the right path.

How can I get the values I set for custom properties in AddressBook to persist?

I have created a custom property in AddressBook named "Qref". I can check it's there using [ABPerson properties], and it's always there for any test app I write.
By the way, you can't remove custom properties, because [ABPerson removeProperties] hasn't been implemented. Let me know if it ever is, because I need to remove one whose name I mistyped.
I set a property value with this code.
ABPerson *p = <person chosen from a PeoplePicker>;
NSError *e;
if (![p setValue: aString forProperty:#"Qref" error:&e]) {
[NSAlert alertWithError:e]runModal;
}
(I have never seen the alert yet, but sometimes get a heap of error messages in the console.)
At this point I can navigate away from the person in the PeoplePicker and return to find the value correctly set.
If I check [[ABAddressBook sharedAddressBook] hasUnsavedChanges] the result is NO, so clearly changing a custom property value doesn't count as a change, so I force a save by inserting dummy person (please suggest a better way), then executing
[[ABAddressBook sharedAddressBook] save];
The dummy person appears immediately in AddressBook if it is running, so something's right. But when I close my app and run it again, I find the values I set have gone.
(MacOSX-Lion)
I've been barking up the wrong trees. It's turning out that I couldn't save any properties, irrespective of whether they were custom ones or not. Then I wondered if it was something to do with code-signing, entitlements or iCloud, which is impenetrable jungle to me.
It seems the person you get from a PeoplePicker isn't associated with any address book so [[ABAddressBook sharedAddressBook] save] will do nothing. You have to get your person from an ABAddressBook instance. Here's the skeleton of what works, without any error checking.
ABPerson *p = [[myPeoplePicker selectedRecords] objectAtIndex:0];
NSString *uid = p.uniqueId;
ABPerson *editablePerson =
(ABPerson*) [[ABAddressBook sharedAddressBook] recordForUniqueId:uid];
// Typecast because it returns an ABRecord. Can anyone improve?
[editablePerson setValue:#"ABCD" forProperty:#"Qref"]; // my custom property
[[ABAddressBook sharedAddressBook] save];

Resources