How to change marked text format of the IMKTextInput? - cocoa

By default implementors of the IMKTextInput protocol displays marked text of the current input session underlined (with 2 pixel black underline according to documentation). I develop specific Input Method and would like to use another formatting, say, without underlining but with background color. I've tried attributed string with empty format:
NSString *buffer = /* getting some buffered text */;
NSMutableAttributedString *text = [[[NSMutableAttributedString alloc] initWithString:buffer attributes:[NSDictionary dictionary]] autorelease];
// client is of type id<IMKTextInput>, of course
[client setMarkedText:text selectionRange:NSMakeRange(0, [text length]) replacementRange:NSMakeRange(NSNotFound, NSNotFound)];
but with no avail. So, how can I change format of marked text? Is it possible?

Related

NSAttributedString from localized string with format specifier

I have a localized string:
"%# some text" = "%# some text";
The format specifier %# may appear in any location in the localized string.
The problem is this string should be an NSAttributedString; the %# replacement and the rest of the text should have different attributes. How can I solve this issue?
1) Get your localized template using NSLocalizedString().
2) Get the text to insert.
3) Combine the two using -stringWithFormat:.
4) In the template, find the location of the placeholder using -rangeOfString:
5) Find the range of the inserted text in the formatted string, using the start position found in the last step, with -rangeOfString:options:range:. (The third argument here is the range within which to search; this avoids finding non-substituted text.)
6) Create an attributed string from the formatted string, using the range to apply attributes to the inserted text.
You can use NSMutableAttributedString for this case. Here is Apple documentation
NSString *textToDisplay = [NSString stringWithFormat:#"%# somet text",localizedString];
NSMutableAttributedString *attrStr = [[NSMutableAttributedString alloc] initWithString:textToDisplay];
[attrStr addAttribute:NSFontAttributeName
value:[UIFont fontWithName:#"Exo2-Regular" size:30]
range:NSMakeRange(0, locatilzedString.length)];
[attrStr addAttribute:NSFontAttributeName
value:[UIFont fontWithName:#"Exo2-Bold" size:30]
range:NSMakeRange(locatilzedString.length, attrStr.length)];
label.attributedText = attrStr

Proper way to insert a hyperlink in a text control

ALL,
I am looking for a way to properly insert a hyperlink into the NSTextView.
Trying to do that I found this link which explains how to make it work. However trying to set the following text:
abc http://www.google.com
I failed to do so with the following code:
std::string test = "abc \"http://www.google.com\"";
int pos1 = test.find( "http://" );
std::string temp1 = test.substr( 0, pos1 );
[tv setString:temp1];
std::string url_text = test.substr( pos1 );
NSMutableAttributedString string = [[NSMutableAttributedString alloc] init];
NSURL *url_obj = [NSURL URLWithString:url];
[string appendAttributedString:[NSAttributedString hyperlinkFromString:url_text withURL:url_obj];
[[tv textStorage] setAttributedString: string];
[string release];
however, the underlining and the blue foreground starts from the very first position of the text, i.e. the whole text is underlined, and not just a link.
So looking further I found I can just simply do this. But neither setting the above string, nor trying to type the link gives me the appropriate visible representation of the link - blue and underlined font.
So now my questions are:
Is it possible to do an automatic parsing of the hyperlink in NSTextView when I both set the text and type the text manually?
If not, what is the problem with the code above and why I see the link on all string and not just the text of the link?
I hope I will not need to do any manual work and everything will be automatic.
Thank you and sorry for such a big post.

Replace NSTextattachement Image on NSMutableattributedString

I'm building an UITextView with text and images (Subclassing NSTextstorage for displaying my content)
I'm having textcontent with images URLs.
So my problem is that i need download all the images if they're not cached.
So i want to first insert a placeholder image, download the image and then replace the placeholder image by the downloaded one.
Here's how i do my stuff.
First, i'm formatting my text with images url by replacing all urls with this tag :
[IMG]url[/IMG]
Then i'm using a regex to get all these tags.
I'm testing if there's a cached image or not. If not, i extract all the urls, download them and cache them.
I've created an NSObject class ImageCachingManager and declared a delegate method called when an image has been downloaded :
#protocol ImageCachingManagerDelegate <NSObject>
- (void)managerDidCacheImage:(UIImage *)image forUrl:(NSString *)url;
#end
Like this, I tough that I could use the url of the image got by the delegate method to search the matching url in my NSTextstorage attributedString and replace the current NSTextattachement image by the downloaded one.
But I don't know how to do that...
Thanks for help !
I'm working on something very similar to this at the moment and think this might help. The code is very much alpha but hopefully it will get you to the next step - I'll step through:
Overall Cycle
1. Find you image tags in the full text piece using Reg Ex or XPath - personally i find Hppl to be more powerful but if your content is well structured and reliable, regex is probably fine.
https://github.com/topfunky/hpple
Reduce the space of this match to 1 character and store that range - A textAttachment occupies only 1 character of space within a textview so it's best to reduce this to 1 otherwise when you replace your first match of characters in a range with the first textattachment the next range marker becomes out of date which will lead to issues. Depending on how much processing you need to do this text input during init, this is an important step, i have to do a lot of processing on the text and the ranges change during this parsing so I created an array of special characters that I know is never going to be in the inputs and push these single characters into the reserved space, at the same time i store this special character and the src of the image in an array of a very simple NSObject subclass that stores the SpecialChar, ImgSrc plus has space for the NSRange but i basically find the special character later in the process again because it has been moved about since this point and then set the nsrange at the very end of processing - this may not be necessary in your case but the principle is the same; You need a custom object with NsRange (which will become a text attachment) and the imgSource.
Loop through this array to add placeholder imageAttachments to your attributed string. You can do this by adding a transparent image or a 'loading' image. You could also check your cache for existing images during this point and skipping the placeholder if it exists in cache.
Using your delegate, when the image is successfully downloaded, you need to replace the current attachment with your new one. By replacing the placeholder in the range you've already stored in your object. Create a placeholder attributedString with the NSTextAttachment and then replace that range as below.
Some sample code:
Steps 1 & 2:
specialCharsArray = [[NSArray alloc]initWithObjects:#"Û", #"±", #"¥", #"å", #"æ", #"Æ", #"Ç", #"Ø", #"õ", nil];
//using Hppl
NSString *allImagesXpathQueryString = #"//img/#src";
NSArray *imageArray = [bodyTextParser searchWithXPathQuery:allImagesXpathQueryString];
//
imageRanges = [[NSMutableArray alloc] init];
if([imageArray count]){
for (TFHppleElement *element in imageArray) {
int i = 0;
NSString *imgSource = [[[element children] objectAtIndex:0] content];
NSString *replacementString = [specialCharsArray objectAtIndex:i];
UIImage *srcUIImage = [UIImage imageNamed:imgSource];
[srcUIImage setAccessibilityIdentifier:imgSource]; //only needed if you need to reference the image filename later as it's lost in a UIImage if stored directly
//imagePlacement is NSObject subclass to store the range, replacement and image as above
imagePlacement *foundImage = [[imagePlacement alloc]init] ;
[foundImage initWithSrc:srcUIImage replacement:replacementString];
[imageRanges addObject:foundImage];
i++;
}
Step 3:
-(void)insertImages{
if ([imageRanges count]) {
[self setScrollEnabled:NO]; //seems buggy with scrolling on
int i = 0; //used to track the array placement for tag
for(imagePlacement *myImagePlacement in imageRanges){
// creates a text attachment with an image
NSMutableAttributedString *placeholderAttString = [[NSMutableAttributedString alloc]initWithAttributedString:self.attributedText];
NSTextAttachment *attachment = [[NSTextAttachment alloc] init];
//scales image down to ration of width of view - you probably don't need this
CGSize scaleToView = imagePlacement.imgSrc.size;
scaleToView.width = self.frame.size.width;
scaleToView.height = (self.frame.size.width/imagePlacement.imgSrc.size.width)*imagePlacement.imgSrc.size.height;
attachment.image = [self imageWithColor:[UIColor clearColor] andSize:scaleToView];
NSMutableAttributedString *imageAttrString = [[NSAttributedString attributedStringWithAttachment:attachment] mutableCopy];
[self setAttributedText:placeholderAttString];
i++;
}
}
[self setScrollEnabled:YES];
}
- (UIImage *)imageWithColor:(UIColor *)color andSize:(CGSize) size {
CGRect rect = CGRectMake(0.0f, 0.0f, size.width, size.height);
UIGraphicsBeginImageContext(rect.size);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, [color CGColor]);
CGContextFillRect(context, rect);
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}

Cocoa Text - refreshing text on-the-fly

In an app I'm working on, the user inputs plain text, and the app reformats the text by transforming it to an NSAttributedString, and displays it. This all happens live.
Currently, I'm doing the following on my NSTextView's textDidChange delegate method:
- (void)textDidChange:(NSNotification *)notification {
// saving the cursor position
NSInteger insertionPoint = [[[self.mainTextView selectedRanges] objectAtIndex:0] rangeValue].location;
// this grabs the text view's contact as plain text
[self updateContentFromTextView];
// this creates an attributed strings and displays it
[self updateTextViewFromContent];
// resetting the cursor position
self.mainTextView.selectedRange = NSMakeRange(insertionPoint, 0);
}
While this mostly works, it's not ideal. The text seems to blink for a split second (you especially notice it on the red dots under spelling errors), and when the cursor was previously near one of the edges of the visible rect, it the scroll position gets reset. In my case, this is a very much undesirable side-effect.
So my question is: Is there a better way of doing what I'm trying to do?
I think you have a slight misconception of how an NSTextView works. The user never enters a "plain string", the data store of an NSTextView is always an NSTextStorage object, which is a subclass of NSMutableAttributedString.
What you need to do is add/remove attributes to the existing attributed string that the user is editing, rather than replacing the entire string.
You should also not make changes to the string in the ‑textDidChange: delegate method, as changing the string from that method can cause another change notification.
Instead, you should implement the delegate method ‑textStorageDidProcessEditing:. This is called whenever the text changes. You can then make modifications to the string like so:
- (void)textStorageDidProcessEditing:(NSNotification*)notification
{
//get the text storage object from the notification
NSTextStorage* textStorage = [notification object];
//get the range of the entire run of text
NSRange aRange = NSMakeRange(0, [textStorage length]);
//for example purposes, change all the text to yellow
//remove existing coloring
[textStorage removeAttribute:NSForegroundColorAttributeName range:aRange];
//add new coloring
[textStorage addAttribute:NSForegroundColorAttributeName
value:[NSColor yellowColor]
range:aRange];
}

NSTextView line break on character, not word

I have an NSTextView for editing a long string without spaces, but with punctuation characters. I'd like it to wrap at whatever character falls at the end of the line instead of trying to split it into words where it finds punctuation, which results in uneven lines.
I thought it would be as easy as making a subclass of NSATSTypesetter to reimplement this method like so:
- (BOOL) shouldBreakLineByWordBeforeCharacterAtIndex:(NSUInteger)charIndex {
return YES;
}
This is not having any effect on the layout of the text view. It is being called once for every line break on every layout, but only where the line break would have occurred anyway.
The line breaking setting in a NSTextView is actually a attribute of the text in it's textStorage.
You need to get the NSTextStorage of that view which is a NSMutableAttributedString then set that string's style to your needed line break setting.
Keep in mind the text in the storage keeps changing and the text itself contains the setting so you need to keep setting it on each change.
Call this method below as [self setLineBreakMode:NSLineBreakByCharWrapping forView:txtView]; every time the text storage is changed, e.g.: KVO observe txtView.string
-(void)setLineBreakMode:(NSLineBreakMode)mode forView:(NSTextView*)view
{
NSTextStorage *storage = [view textStorage];
NSMutableParagraphStyle *style = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
[style setLineBreakMode:mode];
[storage addAttribute:NSParagraphStyleAttributeName value:style range:NSMakeRange(0, [storage length])];
[style release];
}

Resources