Tokens/ViewController.m
/* |
Copyright (C) 2016 Apple Inc. All Rights Reserved. |
See LICENSE.txt for this sample’s licensing information |
Abstract: |
Main view controller for this sample. |
*/ |
#import "ViewController.h" |
#import "MyToken.h" |
#import "CustomView.h" |
@interface ViewController () <NSTokenFieldDelegate> |
@property (weak) IBOutlet NSTokenField *tokensField; |
@property (weak) IBOutlet NSPopUpButton *namesPopup; |
@property (weak) IBOutlet NSButton *useTokenMenu; |
@property (weak) IBOutlet NSButton *useCustomTokenMenu; |
@property (strong) IBOutlet CustomView *customMenuView; // menu used for custom menu item views |
@property (strong) NSMenu *tokenMenu; // the menu attached to each token |
@property (strong) NSMenu *customMenu; // the menu attached to each token, but display a custom view from 'customMenuItemView' |
@property (strong) NSMutableArray *builtInKeywords; |
@property (strong) NSArray *matches; |
@property (strong) MyToken *menuToken; |
@end |
#pragma mark - |
@implementation ViewController |
// ------------------------------------------------------------------------------- |
// awakeFromNib |
// ------------------------------------------------------------------------------- |
- (void)awakeFromNib |
{ |
// the default tokenizing character is the comma; carriage return (or newline character) |
// |
// example how to make the tokenize character with "/" |
/* |
NSCharacterSet *set = [NSCharacterSet characterSetWithCharactersInString:@"/"]; |
[self.tokensField setTokenizingCharacterSet:set]; |
*/ |
self.tokensField.tokenStyle = NSTokenStyleDefault; |
self.tokensField.delegate = self; // this can also be done in Interface Builder |
self.tokensField.completionDelay = 0.5; // slow down auto completion a bit for type matching |
// create the token menu (to allow for the user to edit it) |
_tokenMenu = [[NSMenu alloc] initWithTitle:@""]; |
[self.tokenMenu insertItem:[[NSMenuItem alloc] initWithTitle:@"Edit…" |
action:@selector(editCellAction:) |
keyEquivalent:@""] atIndex:0]; |
// build our type completion list of names |
// (copy off the menu item title to a separate array for type completion matching) |
// |
_builtInKeywords = [[NSMutableArray alloc] init]; |
for (NSMenuItem *menuItem in self.namesPopup.menu.itemArray) |
{ |
[self.builtInKeywords addObject:menuItem.title]; |
} |
// create our custom menu item view |
_customMenu = [[NSMenu alloc] initWithTitle:@"Custom"]; |
NSMenuItem *newItem = [[NSMenuItem alloc] initWithTitle:@"Custom" action:nil keyEquivalent:@""]; |
newItem.view = self.customMenuView; |
[self.customMenu insertItem:newItem atIndex:0]; |
} |
#pragma mark - NSTokenFieldDelegate |
// --------------------------------------------------------------------------- |
// styleForRepresentedObject:representedObject |
// |
// Make sure our tokens are rounded. |
// --------------------------------------------------------------------------- |
- (NSTokenStyle)tokenField:(NSTokenField *)tokenField styleForRepresentedObject:(id)representedObject |
{ |
return NSTokenStyleRounded; |
} |
// --------------------------------------------------------------------------- |
// hasMenuForRepresentedObject:representedObject |
// |
// Make sure our tokens have a menu. By default tokens have no menus. |
// --------------------------------------------------------------------------- |
- (BOOL)tokenField:(NSTokenField *)tokenField hasMenuForRepresentedObject:(id)representedObject |
{ |
return self.useTokenMenu.state == NSOnState; |
} |
// --------------------------------------------------------------------------- |
// menuForRepresentedObject:representedObject |
// |
// User clicked on a token, return the menu we want to represent for our token. |
// By default tokens have no menus. |
// --------------------------------------------------------------------------- |
- (NSMenu *)tokenField:(NSTokenField *)tokenField menuForRepresentedObject:(id)representedObject |
{ |
NSMenu *returnMenu = nil; |
if (self.useTokenMenu.state == NSOnState) |
{ |
_menuToken = representedObject; |
returnMenu = (self.useCustomTokenMenu.state == NSOnState) ? self.customMenu : self.tokenMenu; |
} |
else |
{ |
// note: representedObject is the actual NSToken |
// |
if ([representedObject isKindOfClass:[MyToken class]]) |
{ |
returnMenu = (self.useCustomTokenMenu.state == NSOnState) ? self.customMenu : self.tokenMenu; |
} |
} |
return returnMenu; |
} |
// --------------------------------------------------------------------------- |
// completionsForSubstring:substring:tokenIndex:selectedIndex |
// |
// Called 1st, and again every time a completion delay finishes. |
// |
// substring = the partial string that to be completed. |
// tokenIndex = the index of the token being edited. |
// selectedIndex = allows you to return by-reference an index in the array |
// specifying which of the completions should be initially selected. |
// --------------------------------------------------------------------------- |
- (NSArray *)tokenField:(NSTokenField *)tokenField completionsForSubstring:(NSString *)substring indexOfToken:(NSInteger)tokenIndex |
indexOfSelectedItem:(NSInteger *)selectedIndex |
{ |
self.matches = [self.builtInKeywords filteredArrayUsingPredicate: |
[NSPredicate predicateWithFormat:@"SELF beginswith[cd] %@", substring]]; |
return self.matches; |
} |
// --------------------------------------------------------------------------- |
// representedObjectForEditingString:editingString |
// |
// Called 2nd, after you choose a choice from the menu list and press return. |
// |
// The represented object must implement the NSCoding protocol. |
// If your application uses some object other than an NSString for their represented objects, |
// you should return a new instance of that object from this method. |
// |
// --------------------------------------------------------------------------- |
- (id)tokenField:(NSTokenField *)tokenField representedObjectForEditingString:(NSString *)editingString |
{ |
id returnRepresentedObject = nil; |
if (self.useTokenMenu.state == NSOnState) |
{ |
MyToken *token = [[MyToken alloc] init]; |
token.name = editingString; |
returnRepresentedObject = token; |
} |
else |
{ |
NSArray *foundItems = |
[self.matches filteredArrayUsingPredicate: |
[NSPredicate predicateWithFormat:@"SELF beginswith[cd] %@", editingString]]; |
NSString *foundString = foundItems.firstObject; |
if (foundString.length > 0) |
{ |
MyToken *token = [[MyToken alloc] init]; |
token.name = foundString; |
returnRepresentedObject = token; |
} |
} |
return returnRepresentedObject; |
} |
// --------------------------------------------------------------------------- |
// displayStringForRepresentedObject:representedObject |
// |
// Called 3rd, once the token is ready to be displayed. |
// |
// If you return nil or do not implement this method, then representedObject |
// is displayed as the string. The represented object must implement the NSCoding protocol. |
// --------------------------------------------------------------------------- |
- (NSString *)tokenField:(NSTokenField *)tokenField displayStringForRepresentedObject:(id)representedObject |
{ |
NSString *string = nil; |
if ([representedObject isKindOfClass:[MyToken class]]) // check to see if the object is our token class |
{ |
MyToken *token = representedObject; |
string = token.name; |
} |
else |
{ |
string = representedObject; |
} |
return string; |
} |
#pragma mark - Actions |
// --------------------------------------------------------------------------- |
// tokenFieldAction:sender |
// |
// The action-message selector associated with this NSTokenField. |
// Called when the user commits an edit by pressing return key. |
// --------------------------------------------------------------------------- |
- (IBAction)tokenFieldAction:(id)sender |
{ |
NSText *fieldEditor = [self.tokensField currentEditor]; |
NSRange textRange = fieldEditor.selectedRange; |
if (textRange.length > 0) |
{ |
NSString *replacedString = [NSString stringWithString:self.menuToken.name]; |
[fieldEditor replaceCharactersInRange:textRange withString:replacedString]; |
fieldEditor.selectedRange = NSMakeRange(textRange.location, replacedString.length); |
} |
} |
// --------------------------------------------------------------------------- |
// addTokenAction:sender |
// |
// User wants to add a token (from the "Add" button) |
// --------------------------------------------------------------------------- |
- (IBAction)addTokenAction:(id)sender |
{ |
// first find the right name to apply to the token |
NSString *nameStr = self.namesPopup.titleOfSelectedItem; |
// get the array of tokens |
NSArray *array = self.tokensField.objectValue; |
// copy the array so we can modify and add a new one |
NSMutableArray *newArray = [NSMutableArray arrayWithArray:array]; |
MyToken *token = [[MyToken alloc] init]; |
token.name = nameStr; |
[newArray addObject:token]; |
self.tokensField.objectValue = newArray; // commit the edit change |
// force the insertion point after the added token |
NSText *fieldEditor = self.tokensField.currentEditor; |
fieldEditor.selectedRange = NSMakeRange(fieldEditor.string.length, 0); |
} |
// --------------------------------------------------------------------------- |
// editCellAction:sender |
// |
// The user chose "Edit…" from the token menu. |
// --------------------------------------------------------------------------- |
- (IBAction)editCellAction:(id)sender |
{ |
if (self.useCustomTokenMenu.state == NSOnState) |
{ |
// the edit button from the custon view called us to edit the token value, |
// we need to close our custom menu here |
[self.customMenu cancelTracking]; |
} |
NSText *fieldEditor = [self.tokensField currentEditor]; |
NSRange textRange = fieldEditor.selectedRange; |
NSString *replacedString = [NSString stringWithString:self.menuToken.name]; |
[fieldEditor replaceCharactersInRange:textRange withString:replacedString]; |
fieldEditor.selectedRange = NSMakeRange(textRange.location, replacedString.length); |
} |
- (IBAction)useTokenMenu:(id)sender |
{ |
NSButton *useTokenMenuCheckbox = (NSButton *)sender; |
self.useCustomTokenMenu.enabled = (useTokenMenuCheckbox.state == NSOnState); |
} |
@end |
Copyright © 2016 Apple Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2016-06-03