Returning NSTextView's Selection Attributes - objective-c

When you use TextEdit and have a selection of string, it will give you the selection color, font, size and other attributes as you see above. How do you get those text selection attributes? I'm certain that I need to use the selectedTextAttributes method. I have the following lines of code.
- (void)textViewDidChangeSelection:(NSNotification *)notification {
if ([notification object] == textView1) {
...
...
NSMutableDictionary *dict = [[textView1 selectedTextAttributes] mutableCopy];
NSLog(#"%#",dict);
}
}
If I run it, the result is not quite like what I expect.
NSBackgroundColor = "NSNamedColorSpace System selectedTextBackgroundColor";
NSColor = "NSNamedColorSpace System selectedTextColor";
There aren't really useful values that I can use to get the text color of the string selection and other attributes. If I ask Google about selectedTextColor, I don't get much luck.
Thank you for your help.

selectedTextAttributes describe what the selection highlighting looks like, not the attributes of selected text. I looked for quite some time for the answer to this question, and finally found it here:
Attribute String Programming Guide
Some example code. For an NSTextView* named editingView, this gathers an array of NSDictionary objects for all the differently formatted ranges in the selection.
NSMutableArray* attributes = [NSMutableArray array];
NSRange selRange = editingView.selectedRange;
NSRange effectiveRange = NSMakeRange(selRange.location, 0);
while (NSMaxRange(effectiveRange) < NSMaxRange(selRange)) {
[attributes addObject: [editingView.textStorage attributesAtIndex: NSMaxRange(effectiveRange) longestEffectiveRange: &effectiveRange inRange: selRange]];
}

Related

Manage selection in text kit using swift

I want to convert the following void function written in objective-c to swift but it won't work the way I do it. It goes mostly wrong with step 1,2,4 and 5 cause I don't know how I should translate it.
-(void)applyStyletoSelection:(NSString *)style{
// 1. Get the range of the selected text.
NSRange range = [_textView selectedRange];
// 2. Create a new font with the selected text style.
UIFont *styledFont = [UIFont preferredFontForTextStyle:style];
// 3. Begin editing the text storage.
[_textView.textStorage beginEditing];
// 4. Create a dictionary with the new font as the value and the NSFontAttributeName property as a key.
NSDictionary *dict = #{NSFontAttributeName : styledFont};
// 5. Set the new attributes to the text storage object of the selected text.
[_textView.textStorage setAttributes:dict range:range];
// 6. Notify that we end editing the text storage.
[_textView.textStorage endEditing];
}
this is how far I got but I am sure it is wrong:
func applyStyleToSelection(style: NSString) {
//1
let range = textInput.selectedRange
//2
let styledFont = UIFont.preferredFontForTextStyle(style as String)
//3
textInput.textStorage .beginEditing()
//4
let dict: [String] = ["styledFont"]
//5
//Could not find an overload for 'init' that accepts the supplied arguments
textInput.textStorage .setAttributes([String() : dict]?, range: range)
//6
textInput.textStorage .endEditing()
}
Can anyone help me convert this to swift? You would definitely help me a lot!
You can use native swift types: String rather than NSString.
That dict variable is not actually a dictionary in your translation.
Avoid using var for objects you never mutate.
You never actually set the font attributes to your string, that's a big reason why its not working.

NSPredicateEditor & NSExpression - Can the display be different than the value for the predicate?

I have a predicate editor, which the template was generate via the following:
NSArray * test = [NSArray arrayWithObjects:
[NSExpression expressionForKeyPath: #"Abc"],
[NSExpression expressionForKeyPath: #"Def"],
nil];
NSPredicateEditorRowTemplate * template = [[NSPredicateEditorRowTemplate alloc] initWithLeftExpressions: test
rightExpressionAttributeType: NSStringAttributeType
modifier: NSDirectPredicateModifier
operators: [NSArray arrayWithObject:
[NSNumber numberWithUnsignedInteger:NSContainsPredicateOperatorType]]
options:(NSCaseInsensitivePredicateOption|NSDiacriticInsensitivePredicateOption)];
So if I fill in a predicate editor like this:
When I log out the generated predicate I get:
Abc CONTAINS[cd] "abc" OR Def CONTAINS[cd] "def"
What I'm wondering is if I can somehow have the predicate editors template display be different than the value that gets set in the generated predicate.
EX: I want the output predicate to have:
Field1 CONTAINS[cd] "abc" OR Field2 CONTAINS[cd] "def"
Even though the editor still displays abc and def as the fields. Is this possible?
Yes, you can do this.
You want the array of left expressions to be the actual keyPaths in the final predicate. In your case, "Field1" and "Field2".
As for making a different value appear in the popup, here's where a mind-bending concept comes in:
You're going to localize your predicate editor into English.
There are two ways you could do this.
With a .strings file
With an NSDictionary
With a .strings file
In your source, you would include the following in a comment:
// NSLocalizedStringFromTable(#"%[Field1,Field2]# %[contains]# %#", #"PredicateEditor", #"")
When you run genstrings on your source code, this will generate a PredicateEditor.strings file with the following entries:
"%[Field1]# %[contains]# %#" = "%[Field1]# %[contains]# %#";
"%[Field2]# %[contains]# %#" = "%[Field2]# %[contains]# %#";
You would change the values to be:
"%[Field1]# %[contains]# %#" = "%[Abc]# %[contains]# %#";
"%[Field2]# %[contains]# %#" = "%[Def]# %[contains]# %#";
Then, when you create your NSPredicateEditor, you would set the formattingStringsFileName property to "PredicateEditor", and the editor will take care of the rest.
With an NSDictionary
This would follow the same fundamental concepts as the .strings option, except that you would essentially do:
NSDictionary *formatting = #{
#"%[Field1]# %[contains]# %#" : #"%[Abc]# %[contains]# %#",
#"%[Field2]# %[contains]# %#" : #"%[Def]# %[contains]# %#"
}
[myPredicateEditor setFormattingDictionary:formatting];
That's all you have to do.
I blogged about this a long time ago, and that has more information that you might find useful.
Basically you want to modify the title of the menu items in your popup button. That's all you need to do. It shouldn't effect the underlying predicate that you get returned. If you created it in interface builder it's easy to get at the menu items of a template and set their title. But since you did this in code you'll have to fix it in code.
Here's how you might do that. In my row template class I wanted to change the width of my NSTextFields. So in my row template class I look for them and modify them like this...
- (void)awakeFromNib {
NSArray* views = [self templateViews];
for (id view in views) {
if ([[view class] isEqual:[NSTextField class]]) {
NSRect tfFrame = [view frame];
tfFrame.size.width = 600;
[view setFrame:tfFrame];
}
}
}
You can see that I get the templateViews and look for the NSTextFields... and then modify them. You could do something similar looking for NSPopupButtons. Once you found one check their menu item titles and look for the ones titled "abc" and "def" and change their title to "Field1" and "Field2" respectively.
Rather than using NSLocalizedString with the option literals in the specific format and genstrings to generate the localization strings, it seems easier/cleaner to generate the final localization .strings file strings yourself.
From this blog post, we can use the Private API _generateFormattingDictionaryStringsFile to get the formatting strings from the NSPredicateEditor itself:
extension NSPredicateEditor {
func formattingDictionaryStrings() -> String? {
var strings: String? = nil
if let formattingDictionaryData = self.perform(Selector("_generateFormattingDictionaryStringsFile"))?.takeRetainedValue() as? Data {
strings = String(data: formattingDictionaryData, encoding: .utf16)
}
return strings
}
}
This generates all of the permutations, skipping the need for genstrings. You then replace the tokens to the right of the = with your user-displayed strings.
"%[ABC]# %[is]# %[123]#" = "%1$[ABC]# %2$[is]# %3$[123]#";
"%[ABC]# %[is]# %[456]#" = "%1$[ABC]# %2$[is]# %3$[456]#";
"%[ABC]# %[is]# %[789]#" = "%1$[ABC]# %2$[is]# %3$[789]#";
"%[ABC]# %[contains]# %[123]#" = "%1$[ABC]# %2$[contains]# %3$[123]#";
"%[ABC]# %[contains]# %[456]#" = "%1$[ABC]# %2$[contains]# %3$[456]#";
"%[ABC]# %[contains]# %[789]#" = "%1$[ABC]# %2$[contains]# %3$[789]#";
Even better, you can construct the code that builds each NSPredicateEditorRowTemplate to take as input both the keypath used internally in the predicate and the localized string for that option.
Your method can then generate the strings above, but with the correct localization already inserted.
NOTE: calling this _generateFormattingDictionaryStringsFile private API method seems to cause random crashes within the NSPredicateEditor:
this class is not key value coding-compliant for the key rowType.
So be sure to run it once when needed, but don't leave it active when you're normally running or testing your app.
Yes, it is all a matter of localization, thanks to the fact that the objects are menu items. And they can be treated as such easily.
All you need to do is ...
localize your application.
Then enter the .strings file and change the value to what you want to be displayed
or...
use tools for managing/translating localized apps.
Here is an example of changing things directly in a .strings file:
Change is to ist and booktitle to Buchtitel
/* Class = "NSMenuItem"; title = "is"; ObjectID = "G1c-st-GEK"; */
"G1c-st-GEK.title" = "ist";
/* Class = "NSMenuItem"; title = "booktitle"; ObjectID = "nQh-54-5Nx"; */
"nQh-54-5Nx.title" = "Buchtitel";
Remark: The best way to find the line to change is by looking for the ObjectID. This can be found for each MenuItem by the UIB identity inspector:

Truncate a string

I have a NSTableView that shows the path of files in one column. When the user resizes the tableview I want the pathname (e.g. /Users/name/testfile.m) to be resized, but I want the end of the pathname (e.g. ...name/testfile.m) to be visible and not the start (e.g. /Users/test/te...) of the path as happens by default. I wrote a function that successfully does what I want to do, but the tableview flickers while redrawing as the user scales the tableview. I think there must be a better, more elegant algorithm for doing this, but I have looked into the documentation for NSString and on Stackoverflow and I cant find anything that gives a better solution. If anyone has a more elegant solution to this problem that would be greatly appreciated. Thanks! Cheers, Trond
My current function:
-(NSString *) truncateString:(NSString *) myString withFontSize:(int) myFontSize withMaxWidth:(NSInteger) maxWidth
{
// Get the width of the current string for a given font
NSFont *font = [NSFont systemFontOfSize:myFontSize];
CGSize textSize = NSSizeToCGSize([myString sizeWithAttributes:[NSDictionary dictionaryWithObject:font forKey: NSFontAttributeName]]);
NSInteger lenURL =(int)textSize.width;
// Prepare for new truncated string
NSString *myStringShort;
NSMutableString *truncatedString = [[myString mutableCopy] autorelease];
// If the available width is smaller than the string, start truncating from first character
if (lenURL > maxWidth)
{
// Get range for first character in string
NSRange range = {0, 1};
while ([truncatedString sizeWithAttributes:[NSDictionary dictionaryWithObject:font forKey: NSFontAttributeName]].width > MAX(TKstringPad,maxWidth))
{
// Delete character at start of string
[truncatedString deleteCharactersInRange:range];
}
myStringShort = [NSString stringWithFormat:#"...%#",truncatedString];
}
else
{
myStringShort=myString;
}
return myStringShort;
}
The typical approach would be simply:
[tableViewCell setLineBreakMode:NSLineBreakByTruncatingHead];
As Dondragmer noted, this property may also be set in Xcode's NIB editor.

Is there a way to get Spell Check data from an NSString?

I'm writing a simple shift cipher iPhone app as a pet project, and one piece of functionality I'm currently designing is a "universal" decryption of an NSString, that returns an NSArray, all of NSStrings:
- (NSArray*) decryptString: (NSString*)ciphertext{
NSMutableArray* theDecryptions = [NSMutableArray arrayWithCapacity:ALPHABET];
for (int i = 0; i < ALPHABET; ++i) {
NSString* theNewPlainText = [self decryptString:ciphertext ForShift:i];
[theDecryptions insertObject:theNewPlainText
atIndex:i];
}
return theDecryptions;
}
I'd really like to pass this NSArray into another method that attempts to spell check each individual string within the array, and builds a new array that puts the strings with the fewest typo'd words at lower indicies, so they're displayed first. I'd like to use the system's dictionary like a text field would, so I can match against words that have been trained into the phone by its user.
My current guess is to split a given string up into words, then spell check each with NSSpellChecker's -checkSpellingOfString:StartingAt: and using the number of correct words to sort the Array. Is there an existing library method or well-accepted pattern that would help return such a value for a given string?
Well, I found a solution that works using UIKit/UITextChecker. It correctly finds the user's most preferred language dictionary, but I'm not sure if it includes learned words in the actual rangeOfMisspelledWords... method. If it doesn't, calling [UITextChecker hasLearnedWord] on currentWord inside the bottom if statement should be enough to find user-taught words.
As noted in the comments, it may be prudent to call rangeOfMisspelledWords with each of the top few languages in [UITextChecker availableLanguages], to help multilingual users.
-(void) checkForDefinedWords {
NSArray* words = [message componentsSeparatedByString:#" "];
NSInteger wordsFound = 0;
UITextChecker* checker = [[UITextChecker alloc] init];
//get the first language in the checker's memory- this is the user's
//preferred language.
//TODO: May want to search with every language (or top few) in the array
NSString* preferredLang = [[UITextChecker availableLanguages] objectAtIndex:0];
//for each word in the array, determine whether it is a valid word
for(NSString* currentWord in words){
NSRange range;
range = [checker rangeOfMisspelledWordInString:currentWord
range:NSMakeRange(0, [currentWord length])
startingAt:0
wrap:NO
language:preferredLang];
//if it is valid (no errors found), increment wordsFound
if (range.location == NSNotFound) {
//NSLog(#"%# %#", #"Valid Word found:", currentWord);
wordsFound++;
}
else {
//NSLog(#"%# %#", #"Invalid Word found:", currentWord);
}
}
//After all "words" have been searched, save wordsFound to validWordCount
[self setValidWordCount:wordsFound];
[checker release];
}

NSTextView syntax highlighting

I'm working on a Cocoa text editor which uses an NSTextView. Is it possible to change the color of certain portions of the text?
You should add your controller as the delegate of the NSTextStorage object of the NSTextView ([textView textStorage]) and then implement the delegate method ‑textStorageDidProcessEditing:. This is called whenever the text changes.
In the delegate method you need to get the current NSTextStorage object from the text view using the -textStorage method of NSTextView. NSTextStorage is a subclass of NSAttributedString and contains the attributed contents of the view.
Your code must then parse the string and apply coloring to whatever ranges of text are interesting to you. You apply color to a range using something like this, which will apply a yellow color to the whole string:
//get the range of the entire run of text
NSRange area = NSMakeRange(0, [textStorage length]);
//remove existing coloring
[textStorage removeAttribute:NSForegroundColorAttributeName range:area];
//add new coloring
[textStorage addAttribute:NSForegroundColorAttributeName
value:[NSColor yellowColor]
range:area];
How you parse the text is up to you. NSScanner is a useful class to use when parsing text.
Note that this method is by no means the most efficient way of handling syntax coloring. If the documents you are editing are very large you will most likely want to consider offloading the parsing to a separate thread and/or being clever about which sections of text are reparsed.
Rob Keniger's answer is good, but for someone looking for a more concrete example, here's a short syntax highlighter I wrote that should highlight RegEx template syntax. I want \ to be gray, with the character immediately following them to be black. I want $ to be red, with a digit character immediately following the $ to also be red. Everything else should be black. Here's my solution:
I made a template highlighter class, with a header that looks like this:
#interface RMETemplateHighlighter : NSObject <NSTextStorageDelegate>
#end
I initialize it in the nib file as an object and hook it up to my view controller with an outlet. In awakeFromNib of the view controller, I have this (where replacer is my NSTextView outlet and templateHighlighter is the outlet for the class above):
self.replacer.textStorage.delegate = self.templateHighlighter;
And my implementation looks like this:
- (void)textStorageDidProcessEditing:(NSNotification *)notification {
NSTextStorage *textStorage = notification.object;
NSString *string = textStorage.string;
NSUInteger n = string.length;
[textStorage removeAttribute:NSForegroundColorAttributeName range:NSMakeRange(0, n)];
for (NSUInteger i = 0; i < n; i++) {
unichar c = [string characterAtIndex:i];
if (c == '\\') {
[textStorage addAttribute:NSForegroundColorAttributeName value:[NSColor lightGrayColor] range:NSMakeRange(i, 1)];
i++;
} else if (c == '$') {
NSUInteger l = ((i < n - 1) && isdigit([string characterAtIndex:i+1])) ? 2 : 1;
[textStorage addAttribute:NSForegroundColorAttributeName value:[NSColor redColor] range:NSMakeRange(i, l)];
i++;
}
}
}
So there you go, a fully working example. There were a few details that had me tripped up for ~10 minutes, like the fact that you have to take the string out of textStorage to access the individual characters... maybe this save other people a few minutes.
I recommend you to start by reading the CocoaDev page about Syntax Highlighing. A lot of people have come with solutions for various goals.
If you want to perform source code syntax highlighting, I suggest you to take a look at the UKSyntaxColoredTextDocument from Uli Kusterer.
Sure. You can give the NSTextView an NSAttributedString, and some of the stuff you can do with the attributed string is apply colors to certain subranges of the string.
Or you can search on Google and see that a lot of people have done stuff with this before.
I'd probably recommend using OkudaKit.
If you are ok with WebView you can use https://github.com/ACENative/ACEView
It loads ACE editor in WebView