Proper way to insert a hyperlink in a text control - cocoa

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.

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

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;
}

Change the Color of specific substrings in NSTextView

I want to change the color of specific text in NSTextView. The method should check that after a keydown event.
For example: the word void is finished and the string void changes the
color to blue. Like a code editor.
I searched for a long time but don't find anything.
My Code:
NSRange range = [text rangeOfString:#"void"];
NSString *substring = [[text substringFromIndex:NSMaxRange(range)] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
//I think, here is the mistake.
NSAttributedString *now = [NSAttributedString initWithString:substring];
[now setTextColor:[[NSColor blueColor]]];
I have read that i have to use a NSAttributedString but i don't know how I can get this class from a string.
I'am quite new in cocoa programming.
Thanks for every help!
You can do this way:
NSString *str = #"Hello. That is a test attributed string.";
//in place of NSMakeRange put your range
[self.textview setRichText:YES];
[self.textview setString:str];
[self.textview setTextColor:[NSColor redColor] range:NSMakeRange(3,5)];

Premature line wrapping in NSTextView when tabs are used

I have a strange problem with NSTextView linewrapping after the 51st column if I enter a line of tabs. This only happens with tabs, not with any other character, which wrap correctly at the edge of the text view, not after the 51st character.
This is easy to repeat. Create a blank project in XCode with a single window and just one NSTextView. The only non-default settings are that I have removed constraints, and used the old style autosize to autosize the textview so that it fills the window. I have written no code. Now run the application, open up the window so that is much wider than 51 characters, hold down the tab key and it will wrap early.
Thanks in advance.
The issue here is that NSTextView has a default NSMutableParagraphStyle object which has a list of attributes such as line wrapping, tab stops, margins, etc... You can see this by going to the Format menu, text subview, and select the "Show Ruler" menu. (You get this menu for free with any NSTextView).
Once you show the ruler you will see all of your tab stops and this will explain why your tabs are wrapping once you reach the last tab stop.
So the solution you need is to create an array of tabs that you want for your paragraph style object and then set that to be the style for the NSTextView.
Here is a method to create tabs. In this example, it will create 5 left aligned tabs, each 1.5 inches apart:
-(NSMutableAttributedString *) textViewTabFormatter:(NSString *)aString
{
float columnWidthInInches = 1.5f;
float pointsPerInch = 72.0f;
NSMutableArray * tabArray = [NSMutableArray arrayWithCapacity:5];
for(NSInteger tabCounter = 0; tabCounter < 5; tabCounter++)
{
NSTextTab * aTab = [[NSTextTab alloc] initWithType:NSLeftTabStopType location:(tabCounter * columnWidthInInches * pointsPerInch)];
[tabArray addObject:aTab];
}
NSMutableParagraphStyle * aMutableParagraphStyle = [[NSParagraphStyle defaultParagraphStyle]mutableCopy];
[aMutableParagraphStyle setTabStops:tabArray];
NSMutableAttributedString * attributedString = [[NSMutableAttributedString alloc] initWithString:aString];
[attributedString addAttribute:NSParagraphStyleAttributeName value:aMutableParagraphStyle range:NSMakeRange(0,[aString length])];
return attributedString;
}
Then you invoke it before you add any text to your NSTextView in order to set the default paragraph style with those tab stops in it:
[[mainTextView textStorage] setAttributedString:[self textViewTabFormatter:#" "]];
You can find a additional informatio here, if you want a deeper tutorial:
http://www.mactech.com/articles/mactech/Vol.19/19.08/NSParagraphStyle/index.html
I'm sharing my experience since I recently had kind of the same type of problem(s)
- when pressing tabs, cursor jumps to the next line after about 10-12 tabs
- when there are multiple lines of text, when pressing tabs the whole paragraph turns into bulleted lines
I used the above method by "Ed Fernandez" and could only resolve the problem when there is no text in the NSTextView initially but when existing saved text is loaded it had above problems
For this I tried the below code from below link (It really worked and solved the both problems)
http://www.cocoabuilder.com/archive/cocoa/159692-nstextview-and-ruler-tab-settings.html
You don't need to call "release" if you are using automatic reference counting.
- (IBAction)formatTextView:(NSTextView *)editorTextView
{
int cnt;
int numStops = 20;
int tabInterval = 40;
NSTextTab *tabStop;
NSMutableDictionary *attrs = [[NSMutableDictionary alloc] init];
//attributes for attributed String of TextView
NSMutableParagraphStyle *paraStyle = [[NSMutableParagraphStyle alloc] init];
// This first clears all tab stops, then adds tab stops, at desired intervals...
[paraStyle setTabStops:[NSArray array]];
for (cnt = 1; cnt <= numStops; cnt++) {
tabStop = [[NSTextTab alloc] initWithType:NSLeftTabStopType location: tabInterval * (cnt)];
[paraStyle addTabStop:tabStop];
}
[attrs setObject:paraStyle forKey:NSParagraphStyleAttributeName];
[[editorTextView textStorage] addAttributes:attrs range:NSMakeRange(0, [[[editorTextView textStorage] string] length])];
}
This tab limit is there due to the "Ruler" concept where it's limited to about 12 tabstops. You can see the Ruler by calling
[editorTextView setRulerVisible:YES];

How to change marked text format of the IMKTextInput?

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?

Resources