How to bind NSImageView by path? - macos

I have a program with tableview/details of core data object.
One of the attributes is an image (it only appears in the details).
I think it's best to save just the path to the image file, rather than the image itself (it seems to me that core data files with images are much bigger than the images, even if there is little more than that...).
Since it is supposed that the user drags the image to the imagewell, I thought that it would be appropriate to bind the imagewell to the array controller (AC) using "Value path" (AC.selection.image).
However, this doesn't do anything (the imagewell accepts the image dragged there, but it keeps there when we change selection).
It seems likely to me that I must implement some "Value Transformer", but not the ones which are available (NSUnarchiveFromData and NSKeyedUnarchiveFromData), because I tried those already...
Am I right in this supposition? And if so, what value transformer would that be? Is it something that I'll have to define? Or is this altogether impossible?
Maybe I should add that I'm still using OSX 10.6, and thus some hypothesis seem to be ruled out...
Thanks

Yes. It is possible.
select the image view
Bind it to your object controller. In that value path field, you have to set the path value as string property

From my experience, the "value path" and "value URL" bindings only work one-way, as in, you can specify the image view's contents with them, but you can't extract the path/URL from a dragged image. The documentation says that the "binding is read-only", which is more than a little ambiguous, but I believe this is what it refers to.
I ended up just using a text box with a path inside, a "Choose…" button, and the image view as merely a preview. If you really want the "Image Well" functionality, however, I'd recommend something like KSImageView (GitHub gist) that grabs the path out of the NSPasteboard and stores/rebroadcasts it. Here's the main method for that functionality (after inheriting from NSImageView):
- (void)concludeDragOperation:(id <NSDraggingInfo>)sender {
NSPasteboard *pboard = sender.draggingPasteboard;
NSString *plist = [pboard stringForType:NSFilenamesPboardType];
if (!plist) return;
NSArray<NSString*> *files =
[NSPropertyListSerialization propertyListWithData:[plist dataUsingEncoding:NSUTF8StringEncoding]
options:NSPropertyListImmutable
format:NULL
error:NULL];
if (files.count == 0) return;
NSDictionary *userInfo = #{#"imagePath": files[0]};
[[NSNotificationCenter defaultCenter] postNotificationName:#"KSImageDroppedNotification"
object:self
userInfo:userInfo];
}

Related

NSStatusItem not showing image and title

I am trying to make a Status Bar Item, the ones like the battery or time machine indicator on top right. The code I have is below
- (void)activateStatusBarItem:(id)object{
NSStatusBar *systemBar = [NSStatusBar systemStatusBar];
NSStatusItem *theItem = [systemBar statusItemWithLength:NSVariableStatusItemLength];
NSImage *statusBarIcon = [NSImage imageNamed:#"icon-sleep.png"];
statusBarIcon.size = CGSizeMake(NSSquareStatusItemLength, NSSquareStatusItemLength);
[theItem setImage:statusBarIcon];
[theItem setTitle:#"abc"];
[theItem setTarget:self];
[theItem setAction:#selector(showHUD:)];
}
This method is called when the app launches. However, I don't see theItem on the menu. It did work once which the same code without the image.
Also when I look into Apple's and there the methods:
- setTitle:
- setImage:
- setTarget:
- setAction:
all say that they are available and depreciated at the same time, look at the image below. Is there any other way, I can do this. I want a window/panel to show up when the Item is clicked, possibly attached to the bar.
You are setting the image size to nonsensical values. NSSquareStatusItemLength is -2. It's a sentinel value, not an actual length.
You would need to use systemBar.thickness to determine the actual size that would be used for a square status item.
Regarding the deprecated methods, in 10.10, NSStatusItem has a new button property. This should be used instead of the now-deprecated view property (which you weren't using). All of the other deprecated properties are now just cover methods that call through to the corresponding methods on the button. It is safe to use those cover methods and they should still work.
You are not keeping a strong reference to the status item. It's just stored in a local variable. If you're using ARC, that means that it is released at the end of your -activateStatusBarItem: method, which removes it from the status bar. So, you're creating it and removing it in rapid succession. You should keep a strong reference, probably in an instance variable. The requirement to keep a strong reference is documented for +[NSStatusBar statusItemWithLength:]:
The receiver does not retain a reference to the status item, so you need to retain it. Otherwise, the object is removed from the status bar when it is deallocated.

setContentMode: for HJManagedImageV is not working for iOS

I am using a third party lib, HJCacheClasses, for loading images asynchronously. Here is the code for the same. It's pretty much straightforward:
NSMutableString *url = #"my url";
HJManagedImageV *asyncImageView = [[HJManagedImageV alloc] initWithFrame:frame];
[asyncImageView setBackgroundColor:[UIColor grayColor]];
[asyncImageView showLoadingWheel];
[asyncImageView setContentMode:UIViewContentModeScaleAspectFill];
[asyncImageView.imageView setContentMode:UIViewContentModeScaleAspectFill];
asyncImageView.url = [NSURL URLWithString:url];
[self.imageManager manage:asyncImageView];
[the_pScrollView addSubview:asyncImageView];
Everything works fine except that the image is centered and it not getting stretched/fitted according to the size of the view (which is of size of full screen). I know the image is small but I need to make it fit in the view to fill the view. but none of the setContentMode are working.
If you have a look at the source code of the HJManagedImageV class, you will notice the author hardcoded the imageView of the HJManagedImageV instance to always be aspect fit. So by default you will never get aspect fill content mode working.
There's an easy way to achieve this though, right after you set the url property of the managed image view, add also a callback to your own class, like so:
myImageView.callbackOnSetImage = (id)self;
And in the callback function set the desired contentMode for the imageView, just like this. This is tested and works for me. Good luck
-(void) managedImageSet:(HJManagedImageV*)mi
{
mi.imageView.contentMode = UIViewContentModeScaleAspectFill;
}
I found the answer myself which I feel is more simpler and strightforward. As Ican mentioned,in the source code of the HJManagedImageV class,the author hardcoded the imageView of the HJManagedImageV instance to always be aspect fit. So I just commented that code... For people who like to do the same follow the following steps
Go to HJManagedImageV.m file
search for a method named
-(void) setImage:(UIImage*)theImage
comment the line
imageView.contentMode = ...
or set the content mode you want to for the imageView. Note: This will set the content mode for all the imageViews using this class. So I prefer to go with first option.
In the code where you are using this class, set the content mode for the HJManagedImageV.

Manual Cocoa Binding not changing Observed KeyPath

I'm changing a cocoa binding programatically. I'm binding a NSTextField's value to the selection of an ArrayController. After I manually change the binding, I'm getting the "not key-value coding compliant for the key.." error, with the key being the old key, not the new one.
Check out the code:
NSTextField *textField = [self listTextField];
NSDictionary *currentBindInfo = [textFieldTableViewCell infoForBinding:NSValueBinding];
NSLog(#"pre-change bindings for textField: %#", currentBindInfo);
/* Change the binding. [Tried unbind: first, no difference] */
[textField bind:NSValueBinding
toObject:[currentBindInfo valueForKey:NSObservedObjectKey]
withKeyPath:#"objectValue.iLifeProductName"
options:[currentBindInfo valueForKey:NSOptionsKey]];
/* Log the info so we can confirm it changed. debugging. */
NSLog(#"post-change bindings for textField: %#", [textFieldTableViewCell infoForBinding:NSValueBinding]);
To troubleshoot, I call 'infoForBinding' before and after the change and it looks to be changed correctly. I can see the old value, then I call bind:toObject... and dump the infoForBinding a second time, and the value has changed for the binding:
2011-07-06 22:36:23.137 My App 2011[14640:407] pre-change bindings for listTextFieldTableViewCell: {
NSObservedKeyPath = "selection.osxProductName";
NSObservedObject = "...sameTextField... 0x4009cc380>";
NSOptions = {...same... };
}
2011-07-06 22:36:23.138 My App 2011[14640:407] post-change bindings for listTextFieldTableViewCell: {
NSObservedKeyPath = "selection.iLifeProductName";
NSObservedObject = "...sameTextField... 0x4009cc380>";
NSOptions = {...same... };
}
But the code is still calling the original key:
2011-07-06 22:36:23.231 My App 2011[14640:407] [ valueForUndefinedKey:]: the entity ILifeVersion is not key value coding-compliant for the key "osxProductName".
--
The NSArrayController is bound to a ManagedObjectContext, the entity name is being changed earlier with this:
[[self listAC] setEntityName:entityName];
Is the original keyValuePath being cached somewhere that I need to clear out? Is there a message like willChange/didChangeValueForKeyValuePath that I need to send to the binding or arrayController when I change the observed keypath?
Ideas?
Thanks!
As #noa pointed out, you’re looking at the binding on the cell, but changing the binding on its control. That’s bound (ahem) to cause problems.
Replace this:
[textField bind:NSValueBinding
toObject:[currentBindInfo valueForKey:NSObservedObjectKey]
withKeyPath:#"objectValue.iLifeProductName"
options:[currentBindInfo valueForKey:NSOptionsKey]];
with this:
[textFieldTableViewCell bind:NSValueBinding
toObject:[currentBindInfo valueForKey:NSObservedObjectKey]
withKeyPath:#"objectValue.iLifeProductName"
options:[currentBindInfo valueForKey:NSOptionsKey]];
And see if it works better.
The explanation for this is a bit arcane, and I’m doing it from memory, so please excuse me if I get some of the details wrong.
Because NSControls and their NSCell works so closely together, you can actually bind to either the control or the cell in most instances, and you’ll get very similar results. That is, there’s code in the control to call the proper methods on its NSCell if the control’s been bound to, and vice-versa.
This means that if, in XIB, you bind to one or the other things will work, which is good. It also means you can bind to a cell in cases where you have multiple cells per view, so that’s good. HOWEVER, it can lead to confusion, because in fact you can actually bind to both your view and its cell, and in fact bind them in different ways, and then they’ll crosstalk.
In your example, I believe you’re adding a second binding to the NSControl in addition to the one on its NSCell. You’re doubly-bound. That’s no good.
In terms of best practice, I try to bind only to NSControls unless I have a good reason to drop down to NSCells. Partly because it matches what I do in XIB, partly because any standard helps reduce exactly this problem, and partly because NSCells are being gently deprecated.

Configuring NSPredicateEditor(RowTemplate) for Spotlight metadata queriesI'

I'm trying to configure an NSPredicateEditor (in Interface Builder) to edit the predicate for an NSMetadataQuery.
As a first step, I'm trying to configure an NSPredicateEditorRowTemplate to accept key path(s) for the left-side expression, trying a single keyPath (kMDItemTextContent) to get started.
I can't figure out how to get all the pieces into IB. I've selected the row template, and set "Left Exprs" to "Key Paths" in the IB Attributes Inspector. But, using Apple's PhotoSearch example as a model, it appears that I should enter a user-readable attribute name (say, "Content") here; I can't figure out how to bind it to "kMDItemTextContent".
I've dissected the (correctly-configured) NIB in PhotoSearch(*), and inside it there is an NSKeyPathExpression specifying a metadata attribute attached to an NSPopUpButton/NSPopUpButtonCell.
I can't figure out where to navigate in IB to find the NSPopUpButton, and I'm not sure what I'd do to bind it to an NSExpression.
Any help appreciated.
(*) In case you're wondering, I got inside the NIB by converting it to a XIB, confirming that it still builds correctly, then examining it with BBEdit.
I've found that working with NSPredicateEditor and friends in Interface Builder is an exceedingly tedious task. For that reason, I do all of my row template configuration in code.
For your situation, it doesn't sound like you need a custom row template subclass, so you could probably just do:
#define NSPERT NSPredicateEditorRowTemplate
NSPERT * template = [[NSPERT alloc] initWithLeftExpressions:[NSArray arrayWithObject:[NSExpression expressionForKeyPath:kMDItemTextContent]]
rightExpressionAttributeType:NSStringAttributeType
modifier:NSDirectPredicateModifier
operators:[NSArray arrayWithObject:
[NSNumber numberWithUnsignedInteger:NSContainsPredicateOperatorType]]
options:(NSCaseInsensitivePredicateOption|NSDiacriticInsensitivePredicateOption)];
Once you've got the template, simply add it to the predicateEditor:
NSMutableArray * templates = [[myPredicateEditor rowTemplates] mutableCopy];
[templates addObject:template];
[template release];
[myPredicateEditor setRowTemplates:templates];
[templates release];
As for translating the "kMDItemTextContent", if it doesn't happen automatically (and I think it might), you could use the NSPredicateEditor localization options to display a different name.

IBPlugin: Adding additional objects on drag from IB Library

I have a list view class that just like NSCollectionView requires an additional prototype item and a prototype view to be of any use.
When dropping an NSCollectionView from the library in Interface Builder those two helper items are automatically created. However I couldn't find a single official Apple document dealing with this use case (describing how its done).
Digging thru the Apple Dev Guides I could however find "ibDidAddToDesignableDocument:".
With the following code I managed to get my auxiliary items created on drop from library:
- (void)ibDidAddToDesignableDocument:(IBDocument *)document {
[super ibDidAddToDesignableDocument:document];
NSView *prototypeView = [[[NSView alloc] initWithFrame:NSMakeRect(0.0, 0.0, 300, 65.0)] autorelease];
DLListViewItem *prototypeViewItem = [[[DLListViewItem alloc] initWithNibName:nil bundle:nil] autorelease];
[document addObject:prototypeViewItem toParent:nil];
[document addObject:prototypeView toParent:nil];
[document connectOutlet:#"view" ofSourceObject:prototypeViewItem toDestinationObject:prototypeView];
[document connectOutlet:#"listView" ofSourceObject:prototypeViewItem toDestinationObject:self];
[document connectOutlet:#"prototypeItem" ofSourceObject:self toDestinationObject:prototypeViewItem];
}
However…
…IB adds those aux items for NSCollectionView only on the actual initial drag from the library, not on any other call of "ibDidAddToDesignableDocument:", such as when embedding, copying or duplicating the item. (while my method would, and on all)
This makes me wonder whether Apple actually uses "ibDidAddToDesignableDocument:" for this and if I'm on the right track with this at all.
How does one imitate this properly? I'm having a hard time trying to distinguish between different contexts for "ibDidAddToDesignableDocument:". Anybody successfully done this?
Unfortunately none of Google, Google Code, GitHub, or the documentation revealed anything helpful, so I'm in desperate need of help here. :(
Thanks in advance!
Edit: Oh great, this question just brought me the tumbleweed badge, yay! Not.
I'm more into useful answers actually, but thanks anyway ;)
I struggled with this on a plugin I did myself a while ago. In my case I was able to check a property of the object to see if it had been initialized already and skip adding the auxilliary objects in that case. I believe BWToolkit uses some internal checking that is similar. Couldn't you check your object's 'prototypeItem' property to see if you need to skip creating your aux objects?

Resources