Custom NSFormatter in Objective C - objective-c

I am trying to write my own custom formatter in Objective C by subclassing NSNumberFormatter. Specifically what I'd like to do is make a number turn red if it is above or below certain values. The apple documentation says
For example, if you want negative financial amounts to appear in red, you have this method return a string with an attribute of red text. In attributedStringForObjectValue:withDefaultAttributes: get the non-attributed string by invoking stringForObjectValue: and then apply the proper attributes to that string.
Based on this advice I implemented the following code
- (NSAttributedString*) attributedStringForObjectValue: (id)anObject withDefaultAttributes: (NSDictionary*)attr;
{
NSMutableAttributedString *attrString = [[NSMutableAttributedString alloc] initWithString:[self stringForObjectValue:anObject]];
if ([[attrString string] floatValue] < -20.0f) {
[attrString addAttribute:#"NSForegroundColorAttributeName" value:[NSColor redColor] range:NSMakeRange(0, 10)];
return attrString;
} else return attrString;
}
But when I test this all it does is freeze my application. Any advice would be appreciated. Thanks.

I believe this has something to do with your NSRange that you create. I believe your length (10 in your example) is out of bounds. Try getting the length of the string that you use to initialize your NSMutableAttributedString.
For example:
- (NSAttributedString*) attributedStringForObjectValue: (id)anObject withDefaultAttributes: (NSDictionary*)attr;
{
NSString *string = [self stringForObjectValue:anObject];
NSMutableAttributedString *attrString = [[NSMutableAttributedString alloc] initWithString:string];
NSInteger stringLength = [string length];
if ([[attrString string] floatValue] < -20.0f)
{
[attrString addAttribute:#"NSForegroundColorAttributeName" value:[NSColor redColor] range:NSMakeRange(0, stringLength)];
}
return attrString;
}

Here is how I was finally able to implement this. To make it more visible when a number is negative, I decided to make the background of the text red with white text. The following code does work in a NSTextField cell. I'm not sure why the code in my question (and the answer) does not work, addAttribute should work.
- (NSAttributedString *)attributedStringForObjectValue:(id)anObject withDefaultAttributes: (NSDictionary *)attributes{
NSString *string = [self stringForObjectValue:anObject];
NSMutableAttributedString *attrString = [[NSMutableAttributedString alloc] initWithString:string];
NSInteger stringLength = [string length];
if ([[attrString string] floatValue] < 0)
{
NSDictionary *firstAttributes = #{NSForegroundColorAttributeName: [NSColor whiteColor],
NSBackgroundColorAttributeName: [NSColor blueColor]};
[attrString setAttributes:firstAttributes range:NSMakeRange(0, stringLength)];
}
return attrString;
}

Related

NSMutableAttributedString with NSRange issue

I have the following string
22\nShaʻban\n1435
and i'm using NSMutableAttributedString to format the above string using multiple fonts as follows:
NSString* orgString=#"22\nShaʻban\n1435";
NSMutableAttributedString *attString=[[NSMutableAttributedString alloc] initWithString:[dateStr stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]];
UIFont *dayFont=[UIFont fontWithName:#"Helvetica-Bold" size:40.0f];
UIFont *monthFont=[UIFont fontWithName:#"Arial" size:22.0f];
UIFont *yearFont=[UIFont fontWithName:#"Arial" size:20.0f];
//format day part
[attString addAttribute:NSFontAttributeName value:dayFont range:NSMakeRange(0,2)];
//format month part
[attString addAttribute:NSFontAttributeName value:monthFont range:NSMakeRange(3,[self indexOf:[dateStr substringFromIndex:3] andSearchChar:#"\n"])];
//format year part, app crashes here
[attString addAttribute:NSFontAttributeName value:yearFont range:NSMakeRange([self indexOf:[dateStr substringFromIndex:3] andSearchChar:#"\n"]+1,[dateStr length])];
- (int) indexOf:(NSString*)orgStr andSearchChar:(NSString *)charToSearc {
NSRange range = [orgStr rangeOfString:charToSearc];
if ( range.length > 0 ) {
return range.location;
} else {
return -1;
}
}
i don't know why it crashes when trying to format the last part, i made arrange from the last position in part two +1 to the length of the string, any help please
NSRange NSMakeRange (
NSUInteger loc,
NSUInteger len
);
A range is a location and a length, not a start and end location. So you need to change how you calculate the range content.
Or, split the source string apart, create an attributed string for each part and then append them together.
I'd suggest this:
NSString* orgString=#"22\nShaʻban\n1435";
NSMutableAttributedString *attrString = [[NSMutableAttributedString alloc] init];
UIFont *dayFont = [UIFont fontWithName:#"Helvetica-Bold" size:40.0f];
UIFont *monthFont = [UIFont fontWithName:#"Arial" size:22.0f];
UIFont *yearFont = [UIFont fontWithName:#"Arial" size:20.0f];
NSArray *array = [orgString componentsSeparatedByString:#"\n"];
[attrString appendAttributedString:[[NSAttributedString alloc] initWithString:[array objectAtIndex:0] attributes:#{NSFontAttributeName: dayFont}]];
[attrString appendAttributedString:[[NSAttributedString alloc] initWithString:#"\n"]];
[attrString appendAttributedString:[[NSAttributedString alloc] initWithString:[array objectAtIndex:1] attributes:#{NSFontAttributeName: monthFont}]];
[attrString appendAttributedString:[[NSAttributedString alloc] initWithString:#"\n"]];
[attrString appendAttributedString:[[NSAttributedString alloc] initWithString:[array objectAtIndex:2] attributes:#{NSFontAttributeName: yearFont}]];
So, no NSRange issue. Plus as said by #Wain, you misunderstood what's a NSRange.
Instead of what you're doing, once you found the location, you had to put, as second parameter of NSMakeRange: nextLocation-currentLocation. Id est, for last one, something like this:
NSMakeRange([self indexOf:[dateStr substringFromIndex:3] andSearchChar:#"\n"]+1,
[dateStr length]-[self indexOf:[dateStr substringFromIndex:3] andSearchChar:#"\n"]+1)

NSTextView making bold text using an array

I have an array which has the following structure:
(
[0] = (
[0] = #"Title string"
[1] = #"Some content string"
)
[1] = (
[0] = #"Title string"
[1] = #"Some content string"
)
[2] = (
[0] = #"Title string"
[1] = #"Some content string"
)
...
)
and so on and so fourth to a variating amount of reoccurrence.
My goal is to try and merge it all into one single string to display in an NSTextField, and make every title string bold. So the code above would look something like this if it were outputted.
Title String
Some content string
Title String
Some content string
Title String
Some content string
My first question is how could make a single string where certain text is bold; and my second question is how could I take that string and send it to the NSTextField?
So far this is what I've done.I've tried using NSMutableAttributedString to make the string like so:
NSMutableAttributedString *contentString = [NSMutableAttributedString alloc];
for (int i=0; i<[result count]; i++) {
[contentString addAttribute:NSFontAttributeName
value:[NSFont fontWithName:#"HelveticaNeue-Bold" size:16.0]
range:NSRangeFromString(result[i][0])];
}
But I couldn't figure out how to append anything else to the string, or try and print just that alone because I got this error when trying to the following to display it in the NSTextField.
[self->contentView setString:contentString];
Incompatible pointer types sending 'NSMutableAttributedString *' to paramater of type 'NSString *'
So I tried this instead, but got a different error
[self->contentView attributedString:contentString];
No visible #interface for 'NSTextView' declares the selector 'attributedString'
Managed to find a way to make it work
NSMutableAttributedString *contentString = [NSMutableAttributedString alloc];
for (int i=0; i<[result count]; i++) {
NSMutableAttributedString *resultString = [[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:#"%#\n%#\n\n", result[i][0], result[i][1]]];
NSDictionary *attributes = #{
NSFontAttributeName : [NSFont fontWithName:#"HelveticaNeue-Bold" size:12.0]
};
NSString *subtitle = [NSString stringWithFormat:#"%#", result[i][0]];
[resultString setAttributes:attributes range:NSMakeRange(0, [subtitle length])];
[contentString appendAttributedString:resultString];
}
[self->content setString:#""];
[[self->content textStorage] appendAttributedString:contentString];
The solution lies within the last line of code. All that needed to be done was instead of passing data to setString, use textStorage instead and pass an object to it. (I think I got the terminology right)
Bue yeah hope this helps anyone in the future!
How's this...
NSMutableAttributedString *contentString = [[NSMutableAttributedString alloc] init];
for (int i = 0; i < [result count]; i++) {
NSMutableAttributedString *resultString = [[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:#"%#\n%#\n\n", result[i][0], result[i][1]]];
[resultString addAttribute:NSFontAttributeName
value:[NSFont fontWithName:#"HelveticaNeue-Bold" size:16.0]
range:NSRangeFromString(result[i][0])];
[contentString appendAttributedString:resultString];
}
Some notes about your other code. Make sure to match your [NSMutableAttributedString alloc] with an init like [[NSMutableAttributedString alloc] init]
You should realize that NSMutableAttributedString is not a subclass of NSString, but instead of NSObject, so setString: is not a method available to you.
NSTextView uses insertText: to set it's value.

How to change background only to some words in a textView

I need to change the background color of some particular words in a text that stay in a textView. Something similar to what happens in Firefox when you seearch for a word... So lets say I have a textView with this text
"A man is sitting in front of my porch and another man is calling him"
and I want to change background color to the 2 occurence of the word
"man"
... how could I do that?
I know that there is NSAttributedString to do this kind of things but I can not understand how to modify only some particular words... in the examples I found Googling it there were only examples of how to change the first 5 characters or things like this...
Try this:
NSMutableAttributedString* attrString = [[NSMutableAttributedString alloc] initWithString: #"Your String"];
NSUInteger count = 0;
NSUInteger length = [attrString length];
NSRange range = NSMakeRange(0, length);
while(range.location != NSNotFound)
{
range = [[attrString string] rangeOfString:#"YOURWORD" options:0 range:range];
if(range.location != NSNotFound) {
[attrString addAttribute:NSBackgroundColorAttributeName value:YOURCOLOR range:NSMakeRange(range.location, [word length])];
range = NSMakeRange(range.location + range.length, length - (range.location + range.length));
count++;
}
}

How to make subscripts and superscripts using NSAttributedString?

I need to make subscripts for chemistry formulas (H2O, Na^2+, etc)?
Is this possible to do with NSAttributedString, or is there an alternative/easier way to make subscripts?
Here's what I did in iOS 6. First add the CoreText, and QuartzCore frameworks. Then import:
#import <QuartzCore/QuartzCore.h>
#import <CoreText/CTStringAttributes.h>
#import <CoreText/CoreText.h>
I made a small function that inputs a plain NSString and exports a NSMutableAttributedString with the last character in superscript. This can be modified to allow setting superscript or subscript, change kCTSuperscriptAttributeName value to -1. Also you could add a variable to specify where to put the superscript in the string. Right now it just assumes the end of the string.
- (NSMutableAttributedString *)plainStringToAttributedUnits:(NSString *)string;
{
NSMutableAttributedString *attString = [[NSMutableAttributedString alloc] initWithString:string];
UIFont *font = [UIFont systemFontOfSize:10.0f];
UIFont *smallFont = [UIFont systemFontOfSize:9.0f];
[attString beginEditing];
[attString addAttribute:NSFontAttributeName value:(font) range:NSMakeRange(0, string.length - 2)];
[attString addAttribute:NSFontAttributeName value:(smallFont) range:NSMakeRange(string.length - 1, 1)];
[attString addAttribute:(NSString*)kCTSuperscriptAttributeName value:#"1" range:NSMakeRange(string.length - 1, 1)];
[attString addAttribute:(NSString*)kCTForegroundColorAttributeName value:[UIColor blackColor] range:NSMakeRange(0, string.length - 1)];
[attString endEditing];
return attString;
}
Now when I want to use it I can do the following to put it in a UITextField:
NSString *qlwUnitsPlainText = #"m3";
self.quantityLoadWeightUnits_textField.attributedText = [self plainStringToAttributedUnits:qlwUnitsPlainText];
I hope this helps somebody else, there's not many examples out there!
This is possible to do with NSAttributedString. The attribute constant you're looking for depends on your platform. For Mac OS X it is NSSuperscriptAttributeName and on iOS it is kCTSuperscriptAttributeName. Pass in a negative value for subscript.
The only caveat is that UILabel on iOS can't draw NSAttributedStrings (yet, fingers crossed for iOS 6). You would need to draw the text using Core Text or find some third party replacement for UILabel that can draw an NSAttributedString.
On iOS, I had missed the kCTSuperscriptAttributeName constant but had good results with font size and "baseline". It gives you a little more control too for less obedient fonts:
+ (NSAttributedString *)attributedStringForText:(NSString *)normalText andSuperscript:(NSString *)superscriptText textSize:(CGFloat)textSize
{
UIFont *normalFont = [Styles mainFontWithSize:textSize];
UIFont *superFont = [Styles mainFontWithSize:textSize / 2];
NSMutableAttributedString *finalStr = [[NSMutableAttributedString alloc] initWithString:normalText attributes:#{NSFontAttributeName: normalFont}];
NSAttributedString *superStr = [[NSAttributedString alloc] initWithString:superscriptText attributes:#{NSFontAttributeName: superFont, NSBaselineOffsetAttributeName:#(textSize/2)}];
[finalStr appendAttributedString:superStr];
return finalStr;
}
For SubScript use value for kCTSuperscriptAttributeName as #-1.
As per the document
#discussion Value must be a CFNumberRef. Default is int value 0. If
supported
by the specified font, a value of 1 enables superscripting and a
value of -1 enables subscripting.
extern const CFStringRef kCTSuperscriptAttributeName
CT_AVAILABLE(10_5, 3_2);
Example- [lblHeader setText:#“Headers [Alpha1 – text”];
NSMutableAttributedString *headerSubscript = [[NSMutableAttributedString alloc]initWithAttributedString: lblHeader.attributedText];
[headerSubscript addAttribute:(NSString *)kCTSuperscriptAttributeName value:#-1 range:NSMakeRange(14,1)];
[lblHeader setAttributedText:headerSubscript];
you can also do the following if you want to make it a litle cleaner
NSDictionary *attr = #{ NSFontAttributeName: smallfont,
(NSString*)kCTSuperscriptAttributeName: #1 }
NSRange fabricWeightRange = NSMakeRange(fabricWeight.location + 2, 1);
[subKeyString setAttributes:attr range:fabricWeightRange];

Any way to bold part of a NSString?

Is there any way to bold only part of a string?
For example:
Approximate Distance: 120m away
Thanks!
What you could do is use an NSAttributedString.
NSString *boldFontName = [[UIFont boldSystemFontOfSize:12] fontName];
NSString *yourString = ...;
NSRange boldedRange = NSMakeRange(22, 4);
NSMutableAttributedString *attrString = [[NSMutableAttributedString alloc] initWithString:yourString];
[attrString beginEditing];
[attrString addAttribute:kCTFontAttributeName
value:boldFontName
range:boldedRange];
[attrString endEditing];
//draw attrString here...
Take a look at this handy dandy guide to drawing NSAttributedString objects with Core Text.
As Jacob mentioned, you probably want to use an NSAttributedString or an NSMutableAttributedString. The following is one example of how you might do this.
NSMutableAttributedString *string = [[NSMutableAttributedString alloc] initWithString:#"Approximate Distance: 120m away"];
NSRange selectedRange = NSMakeRange(22, 4); // 4 characters, starting at index 22
[string beginEditing];
[string addAttribute:NSFontAttributeName
value:[NSFont fontWithName:#"Helvetica-Bold" size:12.0]
range:selectedRange];
[string endEditing];
If you do not want to bother with fonts (as not every variation of font contains "Bold"), here is another way to do this. Please be aware, this is currently only available on OS X...:
NSMutableAttributedString *attrString = [[NSMutableAttributedString alloc] initWithString:"Approximate Distance: 120m away"];
[attrString beginEditing];
[attrString applyFontTraits:NSBoldFontMask
range:NSMakeRange(22, 4)];
[attrString endEditing];
The code above gave me crash when I created UILabel with this attributedString.
I used this code and it worked:
NSMutableAttributedString *attrString = [[NSMutableAttributedString alloc] initWithString:string];
NSRange boldedRange = NSMakeRange(0, 1);
UIFont *fontText = [UIFont systemFontOfSize:12]; //[UIFont fontWithName:#"Lato-Bold" size:12];
NSDictionary *dictBoldText = [NSDictionary dictionaryWithObjectsAndKeys:fontText, NSFontAttributeName, nil];
[attrString setAttributes:dictBoldText range:boldedRange];
Swift
Also includes getting the range of the string you want to embolden dynamically
let nameString = "Magoo"
let string = "Hello my name is \(nameString)"
let attributes = [NSFontAttributeName:UIFont.systemFontOfSize(14.0),NSForegroundColorAttributeName: UIColor.black]
let boldAttribute = [NSFontAttributeName:UIFont.boldSystemFontOfSize(14.0)]
let attributedString = NSMutableAttributedString(string: string, attributes: attributes)
let nsString = NSString(string: string)
let range = nsString.rangeOfString(nameString)
if range.length > 0 { attributedString.setAttributes(boldAttribute, range: range) }
someLabel.attributedText = attributedString
To bold a string without hardcoding its font, you can use the StrokeWidth attribute with a negative value:
let s = NSMutableAttributedString(string: "Approximate Distance: 120m away")
s.addAttribute(NSStrokeWidthAttributeName, value: NSNumber(value: -3.0), range: NSRange(22..<26))
An NSString is just a data container. It doesn't contain any details about presentation concerns.
It sounds like what you probably want to do is bold part of the UILabel that is being used to display your string. Which I don't think you can do. But you could always break the UI down into three labels, one for "Approximate Distance:", one for "120 m", and one for "away". Place them in-line with each other and you should get the desired effect.
Another option might be to use a UIWebView and a little bit of markup to display your string with embedded formatting information, as discussed here:
http://iphoneincubator.com/blog/windows-views/display-rich-text-using-a-uiwebview
In Xamarin ios you can bold part of a NSString this way:
public static NSMutableAttributedString BoldRangeOfString (string str, float fontSize, int startRange, int lengthRange)
{
var firstAttributes = new UIStringAttributes {
Font = UIFont.BoldSystemFontOfSize(fontSize)
};
NSMutableAttributedString boldString = new NSMutableAttributedString (str);
boldString.SetAttributes (firstAttributes.Dictionary, new NSRange (startRange, lengthRange));
return boldString;
}
and call this method:
myLabel = new UILabel ();
...
myLabel.AttributedText = BoldRangeOfString("my text", fontSize, startRange, lengthRange);
I coupled #Jacob Relkin and #Andrew Marin answers, otherwise, I got the crashes. Here is the answer for iOS9:
UIFont *boldFont = [UIFont boldSystemFontOfSize:12];
NSString *yourString = #"Approximate Distance: 120m away";
NSRange boldedRange = NSMakeRange(22, 4);
NSMutableAttributedString *attrString = [[NSMutableAttributedString alloc] initWithString:yourString];
[attrString beginEditing];
[attrString addAttribute:NSFontAttributeName
value:boldFont
range:boldedRange];
[attrString endEditing];
I took a look at the official documentation: 1 and 2.
Shorter way using Swift5+
let labelNotes = UILabel() //or UITextView(), etc...
let attributedNotes = NSMutableAttributedString(string: "Bold: some stuff not bold")
attributedNotes.addAttribute(NSAttributedString.Key.font, value: UIFont.boldSystemFont(ofSize: 14), range: NSRange(location: 0, length: 5))
labelNotes.attributedText = attributedNotes
If you don't want to hardcode the font or/and the size try this code for bolding full strings:
NSMutableAttributedString *myString = [[NSMutableAttributedString alloc] initWithString:mainString];
[myString beginEditing];
[myString addAttribute:NSStrokeWidthAttributeName
value:[[NSNumber alloc] initWithInt: -3.f]
range:NSMakeRange(0, [mainString length])];
[myString endEditing];