Can we do this in a Xcode project, giving an array of strings instead of the dictionary?
You can, although it’s not a trivial exercise. Apple has some example Objective-C code here:
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!!
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.
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.
@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.
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!