Show NSPopover from NSTokenField Token on Click - objective-c

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.

Related

NSBrowser selection of row and action of NSBrowserCell

I am having an NSBrowser in one of my windows. It has a checkbox, image and textbox as you see in the screenshot.
I am struggling to do two things:
Change the row selection color. By default it is blue.
Action on the checkbox
The checkbox + image + textbox is added to the subclass of NSBrowserCell like this:
- (instancetype)init
{
self = [super init];
if (self) {
_buttonCell = [[NSButtonCell alloc] init];
[_buttonCell setButtonType:NSButtonTypeSwitch];
[_buttonCell setTarget:self];
[_buttonCell setAction:#selector(cellButtonClick:)];
[_buttonCell setTitle:#""];
_imageCell = [[NSImageCell alloc] init];
_textCell = [[NSTextFieldCell alloc] init];
}
return self;
}
I've added target and Action too, but it is not called.
How do I achieve these two things?
Any guidance will be appreciated.
Caveat: customizing NSBrowser/NSBrowserCell is usually hard. It's a legacy view class that's been essentially abandoned for years, and it has a lot of undocumented quirks.
Having said that, try overriding -highlightColorWithFrame:inView: to use a different color for highlighting the row. It that's all you need, then I think that should do it.
As for the action, NSCell (unlike views and controls) is not a subclass of NSResponder and doesn't perform any automatic mouse event processing. (Cells are just helper objects to draw the view of a control/element.)
You'll probably have to catch the event at the browser view level, then perform hit testing to find the column/row that contains your check box cell. If you find a hit, then post the action message yourself (-sendAction:to:from:)—which is literally what a NSControl view does.

Hide the NSDatePicker with NSClickGestureRecognizer

I am developing an app in Objective-C with Cocoa.
I have an NSDatePicker in my app, which I would like to hide after clicking anywhere in the application.
I have:
NSView *mainView - main NSView containing all application elements.
NSDatePicker *datePicker - picker which I would like to hide.
And I am trying to do it this way:
NSClickGestureRecognizer *click = [[NSClickGestureRecognizer alloc] initWithTarget: self action:#selector(hideCalendar:)];
[mainView addGestureRecognizer: click];
...
-(void) hideCalendar:(NSGestureRecognizer*)rec{
[datePicker setHidden:YES];
}
I know there can be only one NSGestureRecognizer of the each type and every element in mainView stops working as it should. So is there a way to call original action of NSClickGestureRecognizer in the hideCalendar: function?
I also tried implement my own NSGestureRecognizer. But to be honest, I don't really know which functions to implement.
How should I hide the datePicker when I click somewhere outside of it while preserving the original behaviour of the rest of the view elements?
Best regards!

NSTokenField selection list shows empty space while scrolling

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.

Instant / Autosave in IOS

For background: I'm a Windows automation and data translation "expert" (or so they say grins) in my day job. I've been dabbling with Objective-C coding off and on since I bought my first Mac in 2004.
I'm working on an IOS app. My data container class knows how to save and load from disc, and each object responds to an instance method of -(void)saveToImpliedFilename{} or -(void)save:(NSString *)filename {}. There's a static call to load the data files from storage and create distinct data objects from them (they're fairly lightweight objects, so I'm not worried about loading several at a time). The app's domain is such that many of them won't ever be loaded at once anyway.
+(NSArray *)loadData {}
That's all working fine and wonderful. In storage the objects are stored as Xml and life is good.
Where I'm having trouble is when trying to modify the tutorials so that two things happen for me:
Quick note: I'm using the tutorial as a basis for POC coding, then I'll go back and start over with the "real" coding, reusing my data objects and some of the other utility I've built along the way.
Here's my list of goals and the issues:
I want the table view to tell the data objects to save at pretty much every "edit" event. The only one I can consistently get to work is reorganizing the table's order. (the save button and adding a new entry works fine)
entering a new entry into the list creates a nice modal editor with a save and a cancel button which work wonderfully. But if I edit an existing entry, I can't reproduce the save buttons' behaviors. Each time I try, the buttons' events no longer fire. I can't figure out where I'm going wrong.
I'm using the "Editable Table View" project from this tutorial series as my basis: http://www.aboutobjects.com/community/iphone_development_tutorial/tutorial.html
In the following code, the [self isModal] test is where the save/cancel buttons are made visible and wired up. Bringing up the new-entry screen is apparently the only time it's modal. I tried wiring this stuff up so that the buttons were created all the time, but again, the events never fire for either one. The next block below is where the editable table view is called explicitly with the NEW functionality, but the nonModal view of the same tableview is called by the select event on the selector table.
So...
// code snipped for the new/modal editor
- (void)viewDidLoad {
// Uncomment the following line to preserve selection between presentations.
// self.clearsSelectionOnViewWillAppear = NO;
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem;
// If the user clicked the '+' button in the list view, we're
// creating a new entry rather than modifying an existing one, so
// we're in a modal nav controller. Modal nav controllers don't add
// a back button to the nav bar; instead we'll add Save and
// Cancel buttons.
//
if ([self isModal]) {
UIBarButtonItem *saveButton = [[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemSave
target:self
action:#selector(save)];
[[self navigationItem] setRightBarButtonItem:saveButton];
UIBarButtonItem *cancelButton = [[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemCancel
target:self
action:#selector(cancel)];
[[self navigationItem] setLeftBarButtonItem:cancelButton];
}
// do stuff here to display my object...
}
// this code is called from the selection table to explicitly add a new data object.
- (void)add {
vhAddVehicleViewController *controller = [[vhAddVehicleViewController alloc] initWithStyle:UITableViewStyleGrouped];
id vehicle = [[Vehicle alloc] init];
[controller setVehicle:vehicle];
[controller setListcontroller:self];
UINavigationController *newNavController = [[UINavigationController alloc] initWithRootViewController:controller];
[[self navigationController] presentViewController:newNavController animated:YES completion:nil];
}
// this is where it's called on the table selection to show the same view without the save/cancel buttons.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
vhAddVehicleViewController *controller = [[vhAddVehicleViewController alloc] initWithStyle:UITableViewStyleGrouped];
NSUInteger index = [indexPath row];
id vehicle = [[self vehicles] objectAtIndex:index];
[controller setVehicle:vehicle];
[controller setTitle:[vehicle Vehiclename]];
[[self navigationController] pushViewController:controller animated:YES];
}
I'm assuming the issue is that presenting it makes it modal, where as pushing it doesn't...? That's fine. But when I take out the test for modal to try to keep the buttons working, no joy. The buttons draw and click when tapped, but the events don't fire.
HALP! :-)
Thanks much.
-- Chris (I logged in with my Google account so at the top of the page I'm showing as "user1820796") shrug
You forgot to call [super viewDidLoad];
Update
Try removing the cancel button that goes on the left side when pushing the view controller. See if save starts working. I think the problem is you should not add a left button to the navigation bar when the view controller is pushed.
Which method signature are you using?
- (void)save
{
NSLog(#"Saving");
}
Or
- (void)save:(id)sender
{
NSLog(#"Saving");
}
I still think this was related to push/popping the view rather than presenting the view. I switched it all to presentation and it's working how I want now.
Thanks for the assistance guys. Quite a different paradigm than I'm used to on the GUI stuff, but I'm getting there.
thanks!

When is it safe to manipulate screen controls through code in iOS/Objective-C

I have a question regarding iOS (or perhaps more accurately Objective-C) and properties. I have a UIView with a UISegmentedControl, by default it has 3 segments. I have a message which accepts a parameter and based on this parameter I may want to remove one of the segments. In UIView A I do this:
MyViewController *myview = [[[MyViewController alloc] initWithNibName:#"MyViewController" nib:nil] autorelease];
[[self navigationController] pushViewController:myview animate:YES];
[myview showItem:item];
In UIView B this happens in showItem:
-(void) showItem:(Item*)item{
if (item.removeSegment){
[segmentControl removeSegmentAtIndex:0 animate:NO];
}
}
I have noticed that the segment only gets removed when I call showItem after I have pushed it on the navigation controller. When I swap those two line, so I first call showItem and then push the view, the UISegmentedControl still has three segments instead of two.
This just feels wrong, it seems like bad practice that my code will break if someone doesn't call two messages in the right order. Is there a better way to do this? I've been looking at some sort of a property lifecyle that I can use, I am very familiar with this from ActionScript 3, but I have been unable to find anything on the subject.
(as an aside: in AS3 I would make a property, in the setter I don't manipulate any screen controls but call InvalideProperties. My overriden methode CommitProperties will be called once the entire object and child controls have been created. In CommitProperties I check if my property value has changed and this is where I would remove the segment.)
A common way of doing something like this is to create an Item *item property in MyViewController and set that when myview is created. So, your code becomes:
MyViewController *myview = [[[MyViewController alloc] initWithNibName:#"MyViewController" nib:nil] autorelease];
myview.item = item;
[[self navigationController] pushViewController:myview animate:YES];
MyViewController would then use that property in its viewWillAppear: method to configure its own segment control.
I think what you are falling prey to is myview->segmentControl doesn't exist until myview.view is referenced because of the lazy load of the view.
-(void) showItem:(Item*)item{
[self view]; // NO OP TO FORCE LOAD!!
if (item.removeSegment){
[segmentControl removeSegmentAtIndex:0 animate:NO];
}
}
Should work for you. Hope this helps!