Completion in text field

Can we do this in a Xcode project, giving an array of strings instead of the dictionary?

grab-001

You can, although it’s not a trivial exercise. Apple has some example Objective-C code here:

https://developer.apple.com/library/content/samplecode/SearchField/Listings/MyWindowController_m.html

You basically do it by implementing the last three NSControlTextEditingDelegate methods in that example.

FWIW, this is very easy to do in HTML with JavaScript, and there are lots of examples and libraries available. For those that are interested, Keyboard Maestro offers a Custom HTML Prompt action that makes this pretty easy. Also lots of help and examples in the KM Forum Posts about HTML Prompt.

Thanks Jim. But I prefer to the challenge that AppleScriptObjC in Xcode represents to me.
I have several apps that I’ve made with Applescript Studio in Xcode 3.6.
Those apps are still running but I want to add some features and maybe improve their UI.

Ouf! this is high level!

If I understand well, I have to implement:
To set the list of keywords -> control:textView:completions:forPartialWordRange:indexOfSelectedItem:
To get the text entered by user -> controlTextDidChange:notification:
To manage the displayed list of completions -> control:textView:commandSelector

I’ll see if I’m up to it!!

:fearful:

Actually, I think I may have misinterpreted what you want. You have to do all that if you want autocompletion, but if you just want different alternatives to appear when you hit the F5 key, you only need to implement control:textView:completions:forPartialWordRange:indexOfSelectedItem:.

Here’s some code, assuming you’ve made the app delegate also the delegate of the search field:

property theSearchField : missing value -- IBOutlet
property wordList : {}

on applicationWillFinishLaunching:aNotification
	set my wordList to current application's NSArray's arrayWithArray:{"Arthur", "Artist", "Acrobat"} -- whatever
end applicationWillFinishLaunching:

on control:theControl textView:theView completions:theCompletions forPartialWordRange:theRange indexOfSelectedItem:theIndex
	if (theControl's isEqualTo:theSearchField) as boolean then
		set theString to theView's |string|()'s substringWithRange:theRange
		set thePredicate to current application's NSPredicate's predicateWithFormat:"SELF BEGINSWITH[c] %@" argumentArray:{theString}
		set theMatches to wordList's filteredArrayUsingPredicate:thePredicate
		return theMatches
	end if
end control:textView:completions:forPartialWordRange:indexOfSelectedItem:

That’s sounds better.
Even if it’s what I’m trying to do since yesterday (before asking here).

I don’t see where to implement this method.
I thought applicationWillFinishLaunching: was the good place but I can see that it’s not.

And the method itself is totally opaque to me: the only parameters I’m able to set are indexOfSelectedItem: and completions: !!!

I’m lost.

It’s a delegate method. Presuming you have just an app delegate class, you put it there. You then connect theSearchField as an outlet to the field itself, and set the app delegate as the search field’s delegate (control-click, then drag from delegate to the app delegate).

You don’t need to set any. You need the control in case several fields use the same delegate, you need the text view to get its string, and you need the range, so you can work out the start of the current word. The completions passed to you are the default values, so you can do something like add to them rather than replace them, but you’re probably just going to ignore the parameter.

If you want to change the selected index, you have to allow for the fact that it’s an out value, and set its contents. Here’s a trivial example you can insert into the above code:

      if theMatches's |count|() > 2 then set contents of theIndex to 2

It was my first attempt. on control:…
But as I was trying to set the parameters, the method was preventing compilation.
I thought I was on the wrong path…

With your code under the eyes, it seems obvious. :slight_smile:

Can I ask one more question?
Is it possible to send the action continuously, while the user is typing (the equivalent to controlTextDidChange)?

You will need to implement controlTextDidChange:, probably like in the example I pointed to above. You’ll possibly need control:textView:doCommandBySelector:, too. (I’ve only done it in text views via subclassing, which is probably even more complex in this case.)

This might be what you’re looking for:

sendsWholeSearchString
Property
A Boolean value indicating whether the cell calls its search action method when the user clicks the search button (or presses Return) or after each keystroke.

Declaration
OBJECTIVE-C
@property BOOL sendsWholeSearchString
Discussion
When the value of this property is yes, the field calls its action method when the user clicks the search button or presses Return. When the value is NO, the field calls the action method after each keystroke. The default value of this property is no.

Availability
Available in OS X v10.10 and later.

37

@sphil
Can’t make it work.
Maybe because the search field is bound to the application delegate.

@ShaneStanley
All my attempts ends up with a stack overflow.
The action is sent when entering the first char and won’t stop until it overflows.
The first entry is displayed, selected and copied in the field.

grab-002

Here’s some working code:

script AppDelegate
	property parent : class "NSObject"
	
	-- IBOutlets
	property theWindow : missing value
	property theSearchField : missing value -- IBOutlet
	property wordList : {}
	property completePosting : false -- stops recursion
	property commandHandling : false -- flag for non-character stuff like delete
	property inCompleting : false -- flag for whether to complete again
	
	on applicationWillFinishLaunching:aNotification
		set my wordList to current application's NSArray's arrayWithArray:{"Arthur", "Artist", "Acrobat"}
	end applicationWillFinishLaunching:
	
	on control:theControl textView:theView completions:theCompletions forPartialWordRange:theRange indexOfSelectedItem:theIndex
		if (theControl's isEqualTo:theSearchField) as boolean then
			set theString to theView's |string|()'s substringWithRange:theRange
			set thePredicate to current application's NSPredicate's predicateWithFormat:"SELF BEGINSWITH[c] %@" argumentArray:{theString}
			set theMatches to wordList's filteredArrayUsingPredicate:thePredicate
			set my inCompleting to (theMatches's |count|() > 0)
			return theMatches
		end if
	end control:textView:completions:forPartialWordRange:indexOfSelectedItem:
	
	on controlTextDidChange:aNotification
		if inCompleting and not completePosting and not commandHandling then
			set completePosting to true
			set textView to aNotification's userInfo()'s objectForKey:"NSFieldEditor"
			textView's complete:(missing value)
			set completePosting to false
		end if
	end controlTextDidChange:
	
	on control:theControl textView:theView doCommandBySelector:theSelector
		set didIt to false
		if (theControl's isEqualTo:theSearchField) as boolean then
			if theView's respondsToSelector:theSelector then
				set my commandHandling to true
				set theSig to theView's methodSignatureForSelector:theSelector
				set theInvocation to current application's NSInvocation's invocationWithMethodSignature:theSig
				theInvocation's setTarget:theView
				theInvocation's setSelector:theSelector
				theInvocation's invoke()
				set my commandHandling to false
				set didIt to true
			end if
		end if
		return didIt
	end control:textView:doCommandBySelector:

I think he’s after the completion to repeat, not the searching. If so, he probably wants that setting off.

Thank you Shane.

My main mistake in converting the Apple’s example was that I misunderstood the term !self.
I did not see that ! means not.
Inevitably, if the completion is repeated while completePosting is true, it will never end!

Even if my knowledge of ObjC has improved, I’m still a newbie!