Search code examples
swiftautocompletenstokenfield

NSTokenField with suggested tokens using Swift


I'm new to Swift and am coming from from AppleScript Obj-C. I've read through a few books and am getting comfortable with the syntax, but I still feel pretty lost.

I'm trying to create a simple Token Field that suggests autocomplete tokens like Apple Mail does when it recognizes an email in your contacts. My inspiration comes from this ASOC script (post #6). I tried to duplicate it in swift as best I could (without the action menu on tokens):

import Cocoa

@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {

@IBOutlet weak var window: NSWindow!
@IBOutlet weak var tokenField: NSTokenField!
var theNames = [String]()


func applicationDidFinishLaunching(aNotification: NSNotification) {
    tokenField.setDelegate(tokenField.delegate())
    theNames = ["Pomona", "Potomac", "Potable", "Process", "Plow"]
}

func tokenField(tokenField : NSTokenField, completionsForSubstring substring : String, indexOfSelectedItem selectedIndex : UnsafeMutablePointer<Int>) -> [AnyObject]? {
    var thePredicate = NSPredicate(format: "SELF beginswith[cd] %@", substring)
    var matchingNames = (theNames as NSArray).filteredArrayUsingPredicate(thePredicate)
    return matchingNames as Array
}

func tokenField(tokenField : NSTokenField, hasMenuForRepresentedObject representedObject : AnyObject) -> Bool {
    return true
}


func applicationWillTerminate(aNotification: NSNotification) {
    // Insert code here to tear down your application
}


}

So to sum up. As the user types, if the first letter is "p", a menu with "Pomona", "Potomac", "Potable", "Process", "Plow" should pop up below the word. I'm not sure why nothing is popping up.

Any ideas?


EDIT:

Feb 13 2016

Below ioquatix provided the answer to my question but it is beyond my current knowledge level. He did point out a key flaw in my original code is the lack of NSTokenFieldCellDelegate and NSTokenFieldDelegate. Thanks to his help my (simple but limited) solution is:

import Cocoa
@NSApplicationMain 
class AppDelegate: NSObject, NSApplicationDelegate, NSTokenFieldCellDelegate, NSTokenFieldDelegate {

    var names = ["Pat", "Pot"]
    @IBOutlet weak var tokenField: NSTokenField!

    func tokenField(tokenField: NSTokenField, completionsForSubstring substring: String, indexOfToken tokenIndex: Int, indexOfSelectedItem selectedIndex: UnsafeMutablePointer<Int>) -> [AnyObject]? {
        return (names as NSArray).filteredArrayUsingPredicate(NSPredicate(format: "SELF beginswith[cd] %@", substring))
    }

}

Solution

  • I have implemented the auto-completion using the NSTokenFieldDelegate methods:

    import Cocoa
    import CoreData
    
    class PMTagCompletionController : NSObject, NSTokenFieldDelegate, NSTokenFieldCellDelegate {
        var managedObjectContext : NSManagedObjectContext?
        var tagEntityName = "Tag"
    
        func completionsForSubstring(substring : String) -> [String] {
            if let managedObjectContext = self.managedObjectContext {
                let tagEntity: NSEntityDescription? = NSEntityDescription.entityForName(self.tagEntityName, inManagedObjectContext: managedObjectContext)
    
                let request: NSFetchRequest = NSFetchRequest.init()
    
                request.entity = tagEntity;
    
                if let allTags = try! managedObjectContext.executeFetchRequest(request) as? [PMTag] {
                    var tagNames : [String] = []
    
                    let lowercaseSubstring: String = substring.lowercaseString
    
                    for tag: PMTag in allTags {
                        if tag.name.lowercaseString.hasPrefix(lowercaseSubstring) {
                            tagNames.append(tag.name)
                        }
                    }
    
                    return tagNames
                }
            }
    
            return []
        }
    
        func tokenFieldCell(tokenFieldCell: NSTokenFieldCell, completionsForSubstring substring: String, indexOfToken tokenIndex: Int, indexOfSelectedItem selectedIndex: UnsafeMutablePointer<Int>) -> [AnyObject] {
            return self.completionsForSubstring(substring)
        }
    
    
    
        func tokenField(tokenField: NSTokenField, completionsForSubstring substring: String, indexOfToken tokenIndex: Int, indexOfSelectedItem selectedIndex: UnsafeMutablePointer<Int>) -> [AnyObject]? {
            return self.completionsForSubstring(substring)
        }
    }
    

    It uses PMTag instances from CoreData which represent individual tags and are thus used for auto-completion in the tag field. This should be close enough to what you want to get something working.