NSTokenField selection list shows empty space while scrolling - objective-c

While using NSTokenField something strange is happening, as shown in the images below :
As I type A, selection from popup is shown.
I scrolled it
Some more scroll, and it went below the visible area.
This is a behaviour with all the tableviews. The view behind the rows are visible , but it automatically springs to normal position. But not in this case.
It is fine in Mail app, it is working fine.
My implementation is :
Created an NSTokenField.
Set its delegate to AppDelegate.
In the implementation file
-(NSArray *)tokenField:(NSTokenField *)tokenField completionsForSubstring:(NSString *)substring indexOfToken:(NSInteger)tokenIndex indexOfSelectedItem:(NSInteger *)selectedIndex{
return #[#"A",#"B",#"C"];
}
Even the sample code from apple documentation behaves incorrectly.
How can I make it auto-spring or restrict by some code?

What you see in Mail.app is not an actual NSMenu (Apple cheats, shocking!). Turns out, it's actually a custom NSTextField linked to an NSTableView stuck in a transparent window.
It's a fairly old trick to get around the extremely poor version of scrollWheel: NSMenu happens to have implemented. MTTokenField is a mature alternative to pulling your hair out trying to stick a scroll view in an NSMenu.

You need to predicated the substring with the array content.This will list you the exact matching records(this is the plus point). The other is this will avoid you scrolling as well.
You got to change the delegate method in the following way which will fix the issue.
-(NSArray *)tokenField:(NSTokenField *)tokenField completionsForSubstring:(NSString *)substring indexOfToken:(NSInteger)tokenIndex indexOfSelectedItem:(NSInteger *)selectedIndex
{
NSArray *arrayContents = #[#"A",#"B",#"C"];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF beginswith[cd] %#", substring];
return [NSArray arrayWithArray:[arrayContents filteredArrayUsingPredicate:predicate]];
}
Hope this will help you.

Related

menuWillOpen: and menuDidClose: not invoked for NSMenuDelegate

[Edit] as Willeke helpfully points out it's menuDidClose: NOT menuWillClose:. My code actually had that part right. Correcting the post in case someone else finds this researching a similar problem.
I'm sure this is just a Cocoa newbie problem but I've wracked my brain on it for hours. I've read the NSMenu and NSMenuDelegate docs a few times trying to figure out what I'm missing but it looks straight forward.
I have a window controller for a preferences window with a toolbar and three views. The window controller is declared as NSMenuDelegate.
#interface PrefsController : NSWindowController <NSMenuDelegate, NSWindowDelegate, NSOpenSavePanelDelegate>
This issue is a NSPopUpButton on the first view. The menu associated with popupbutton works fine. I can modify, etc. the menu via the associated IBOutlet variable. It's bound to Shared User Defaults Controller for selected value and that works fine.
But the menuWillOpen: and menuDidClose: methods are not invoked when the menu is accessed.
- (void)menuWillOpen:(NSMenu *)menu {
if (menu == myPopupButton.menu) {
[self updateMenuImages:NSMakeSize(32, 32)];
}
}
- (void)menuDidClose:(NSMenu *)menu {
if (menu == myPopupButton.menu) {
[self updateMenuImages:NSMakeSize(16, 16)];
}
}
My apologies for what is almost certainly a dumb mistake on my part, but I'm stumped.
Menu delegates are not used that often, so Apple hasn't made them too easy to set up in Interface Builder. Instead, do this in awakeFromNib:
myPopupButton.menu.delegate = self;

Show NSPopover from NSTokenField Token on Click

I am attempting to show an NSPopover from an NSTokenField token when the token is clicked.
These tokens have a built in way to show a menu, so from a design standpoint, it's not unlike that action.
However, there does not appear to be any (good) way to execute arbitrary code when a token is clicked.
I have managed to slip some code into - tokenField:menuForRepresentedObject:, but it's far from elegant.
Assuming that - tokenField:menuForRepresentedObject: is the only way to execute code when a token is clicked, I still have another problem: getting the NSRect that represents the token, so that the NSPopover can maintain a visual relationship with said token. I've tried to do some string juggling, figure out how tokens come first, etc., but it is far from reliable, and even requires an undocumented method.
Bottom Line: How do I show an NSPopover from the selected token in an NSTokenField?
I think it can't be done (see my endeavours here). The problem is that an individual token is not exposed in such a way that you can reference its bounds in order to hook it up with a popover.
This is what I ended up doing. I'm working on an open-source NSTokenField alternative with this capability built in.
// in an NSWindowController
- (NSMenu *)tokenField:(NSTokenField *)tokenField menuForRepresentedObject:(id)representedObject
{
NSRect displayRect = NSMakeRect(([NSEvent mouseLocation].x - 2.5),
([NSEvent mouseLocation].y - 2.5),
5, 5);
displayRect = [self.window convertRectFromScreen: displayRect];
// establish popover from displayRect ...
}
It looks pretty great, despite feeling very hacked (and being ocasionally 1px off).
Another option is to use the ability of NSMenuItem to support an arbitrary view:
- (NSMenu *)tokenField:(NSTokenField *)tokenField menuForRepresentedObject:(id)representedObject {
NSMenu *menu = [[NSMenu alloc] init];
NSMenuItem *item = [NSMenuItem alloc] init];
[item setRepresentedObject: representedObject];
NSNib *nib = [[NSNib alloc] initWithNibNamed: #"token" bundle: nil];
[nib instantiateWithOwner: item topLevelObjects: nil];
[menu addItem: item];
return menu;
}
Then you can create a User Interface where the File Owner is an NSMenuItem and it is connected to a custom view with everything you wanted in the Popover. You can use the representedObject for binding to values in the nib.
I'm at the very early stages of doing this. I haven't hooked up my NSManagedObjects to the Token Field yet, and I'm just using a string array for testing, but doing it this way does get a Popover like effect, except for the little corner pointer. Seems much nicer and simpler than many of the other hacks I've seen to do this while researching this.

Pulling out UI Slider Name

Suppose I have a UISlider interface element that I connected to a property in my view controller called alphaSlider. Is it possible to pull out the name of that view controller property at runtime?
The approach I tried was this:
NSString *objectName = [slider description];
Here is my approach in more detail
for (UISlider *slider in sliders)
{
NSString *objectName = [slider description];
// Do some stuff with the NSString
}
But I discovered that the description is not what I thought it was (it's just a listing of the slider properties).
Not sure if I understand what you are asking, but it might be slider.tag
So if I understand you correctly, you have a bunch of UISlider interface elements that are wired to a bunch of UISlider IBOutlets in your code, and you want to figure out which one is the one you have at the moment (in your loop).
Have you tried testing for equality with each of your IBOutlets to find the one that matches? You can do this using == because the pointers will be the same.
Otherwise, I don't believe it is possible since the mapping really only exists in the xib file and is accessed at runtime when the view is being built and the IBOutlets are being populated.

Problem with NSCollectionView

I'm quite a rookie when it comes to Cocoa programming, so I hope some experts can hint me on the right direction with a problem I'm facing.
I have a NSCollectionView that is bound to a NSArrayController (A) of elements having two properties: an URL to a picture and a NSArray (B) of elements of another class.
For every element inside the NSArrayController (A) , I load a subview with a NSImageView that should display the image in the URL and a NSTableView that is bound to the elements of the NSArray (B).
Everything works fine, except for one very important thing: the URL to the picture is not immediately available when I create the subview, it becomes available later, but when it does, I don't see the picture loading in the NSImageView.
I would think of a problem in the KVC/KVO implementation, but the strange thing is that the image loads correctly if, when the URL becomes available, the subview is not visible (e.g in a portion of the scrollview that is not displayed).
Example: The NSScrollview size is such that it can display only two subviews at a time. I add three elements to the NSArrayController (A): the first two images don't load, if I scroll down the scrollview to see the third element, I find the image loaded correctly.
Any ideas about what could cause such a strange behaviour?
Thank you in advance
Luca
series.h
#interface Series : NSObject {
#private
NSMutableString * banner;
}
-(Series*)initWithEpisode:(Episode*)ep;
-(void)setBanner:(NSString*)_banner;
#property (retain, readwrite) NSMutableString *banner;
#end
series.m
#implementation Series
#synthesize banner;
-(Series*)initWithEpisode:(Episode*)ep
{
self = [super init];
if(self){
banner = [[NSMutableString alloc]initWithString:#"some invalid URL"];
}
-(void) setBanner:(NSString*)_banner
{
[banner setString:[NSString stringWithFormat:#"some root path/%#", _banner];
}
-(void)dealloc
{
[super dealloc];
[banner release];
}
SeriesListViewController.m
-(void)VideoChangedStatus:(Episode*)ep{
//This is the delegate called by the object Episode when it retrieves the URL via asynchronous call to a webservice
Series *tmp = [[Series alloc]initWithEpisode:ep];
[[seriesList objectAtIndex:[seriesList indexOfObject:tmp]]setBanner:[ep banner]];
}
The binding is done in the subview nib file, to the NSImageView: I set File's Owner of type NSCollectionViewItem and then bind Valueurl to representedObject.banner
I didn't subclass NSCollectionView nor NSCollectionViewItem
After days of trying I found a solution that works: apparently it's not enough to use the setString method, I need to re-intialize the property inside the setBanner method
-(void) setBanner:(NSString*)_banner
{
banner = [NSMutableString[NSString stringWithFormat:#"some root path/%#", _banner]];
}
Still, I'd be very glad to know if someone has an explanation of why setString was causing that strange (to me) problem and why this solution works.
Thank you
Luca
I’m not sure why you’ve declared banner to be a mutable string — it looks like an immutable string would suffice.
At any rate, when you write a custom setter method you need to send -willChangeValueForKey: and -didChangeValueForKey: to ensure KVO (and hence bindings) compliance:
-(void) setBanner:(NSString*)_banner
{
[self willChangeValueForKey:#"banner"];
[banner setString:[NSString stringWithFormat:#"some root path/%#", _banner];
[self didChangeValueForKey:#"banner"];
}

Cocoa NSView subview blocking drag/drop

I have an NSView subclass which registers for drag files in init method like this:
[self registerForDraggedTypes:[NSArray arrayWithObject:NSFilenamesPboardType]];
The drag drop works perfectly fine, but if I add a subview to this view with the exact same frame, it doesn't work any more. My guess is that the subview is block the drag event to go to super view. Can can I avoid that? Thanks
Also, I know I am asking two questions, but I don't want to create a new topic just for this: When I am dragging, my cursor doesn't change to the "+" sign like with other drags, how can I do that?
Thanks again.
UPDATE:
Here's the how I have it set up in my IB:
The DrawView is the custom class I was talking about that registered for draggedtypes. And the Image view simply is a subview, I dragged an image from the media section...
If it helps, here's my relevant code for DragView:
- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender {
return NSDragOperationCopy;
}
- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender {
NSPasteboard *pboard;
pboard = [sender draggingPasteboard];
NSArray *list = [pboard propertyListForType:NSFilenamesPboardType];
if ([list count] == 1) {
BOOL isDirectory = NO;
NSString *fileName = [list objectAtIndex:0];
[[NSFileManager defaultManager] fileExistsAtPath:fileName
isDirectory: &isDirectory];
if (isDirectory) {
NSLog(#"AHH YEA");
} else {
NSLog(#"NOO");
}
}
return YES;
}
The answer to the second part of your question is this:
- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender{
return NSDragOperationCopy;
}
if you return NSDragOperationCopy, you get the mouse badge for a copy operation. (You can and should, of course, not just return NSDragOperationCopy unconditionally; check the objects on the pasteboard to see if you can accept them.)
I'm not sure what the answer to the first part of your question is, because I'm unable to recreate the subview blocking effect.
Okay, the answer is unfortunately that you can't. The image you dragged is contained in an NSImageView, and NSImageViews accept drag events themselves, so it's grabbing the drag event and not doing anything with it. If your subview was a custom class, you could either a) not implement drag and drop, in which case the drags would be passed through; b) implement drag and drop to accept drags for the subview. In this case, you're using a class over which you don't have any control. If all you want it to do is display an image, you should be able to make another NSView subclass that does nothing but draw the image in drawRect:
As mentioned in the comments, NSImageViews have their own drag and drop enabled by default (used for accepting images that are dragged onto the NSImageView). If you don't want to use this behavior and instead want to use the super view's drag and drop behavior, you'll want to unregister the dragging behavior in the NSImageView.
Objc:
[imageView unregisterDraggedTypes];
Swift:
imageView.unregisterDraggedTypes()
(in case anyone else stumbled across this question first and not the linked one in the comments)