I'm trying to add some different text colors to my app to be fused to an image. I've got alot of input that my users would like a rainbow text color and repeat. So for instance the word: stackoverflow would look like this: s=red t=orange a=yellow c=green k=blue o=purple v=pink e=red r=orange f=yellow l=green o=blue w=purple
I can't even begin to think how I can do this in one single UITextView
Does anyone know how I could achieve this as the user is typing? Tips? Example?
I didn't see any other posts on SO regarding rainbow text for iOS. (correct me if im wrong)
You can do using NSAttributedString:
To make it a general method to support OSX and iOS. Now no need to change NSColor to UIColor, use this on both the operating systems.
#if TARGET_OS_IPHONE
typedef UIColor Color;
#elif TARGET_OS_MAC
typedef NSColor Color;
#endif
-(NSAttributedString *)colorfulStringFrom:(NSString *)string{
NSArray *colors = #[[Color redColor],
[Color yellowColor],
[Color greenColor],
[Color blueColor],
[Color purpleColor],
[Color magentaColor]
];
NSMutableAttributedString *attribString = [[NSMutableAttributedString alloc]initWithString:string];
for (NSInteger location=0; location<string.length; location++) {
NSRange range = NSMakeRange(location, 1);
Color *color = colors[location%colors.count];
[attribString addAttribute:NSForegroundColorAttributeName value:color range:range];
}
return attribString;
}
Output:
Related
I am trying to change text font size with using NSAttributedString. But it's size doesn't change.
NSDictionary *attrDict = #{NSFontAttributeName : [UIFont boldSystemFontOfSize:22], NSForegroundColorAttributeName : [UIColor orangeColor]};
NSAttributedString *newAttString = [[NSAttributedString alloc] initWithString:mytext attributes:attrDict];
[result appendAttributedString:newAttString];
Only text color changes. Size of result string is not 22 and also it is not bold.
Instead of applying the attributes with the alloc, init, try doing it after with something like (with a mutable NSAttributedString):
NSMutableAttributedString *newAtt = [[NSMutableAttributedString alloc] initWithString:mytext]; // Allocate a new NSMutableAttributedString with `mytext`
[newAtt addAttributes:#{NSFontAttributeName : [UIFont boldSystemFontOfSize:20],
NSForegroundColorAttributeName: [UIColor orangeColor]}
range:NSMakeRange(0, [result length])]; // add new attributes for the length of the string `mytext`
[result setAttributedText:newAtt];
This answer would vary depending on what result is, I tested it on a UITextView and it worked fine, there is also an attributedText
property on UILabels.
Hope this helps.
You didn't mention what result means at the end of your code. Are you sure you want to "append" it?
Besides, I use this code for setting fonts
[UIFont fontWithName:#"Arial-BoldMT" size:22.0f]
This can be used to set different fonts and sizes respectively.
Hope this helps:)
I have an NSAttributedString in a UITextView and would like to handle the UIContentSizeCategoryDidChangeNotification when working with Dynamic Type and specifically the text styles. All the examples I've seen (IntroToTextKitDemo) address the case where the font is the same for the whole UI element. Does anyone know how to handle this properly so all the attributes update properly?
Note: I asked this on the developer forums when iOS 7 was under NDA. I'm posting it here because I found a solution and thought others might find it useful.
I found a solution. When handling the notification you need to walk the attributes and look for the text styles and update the font:
- (void)preferredContentSizeChanged:(NSNotification *)aNotification
{
UITextView *textView = <the text view holding your attributed text>
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithAttributedString:textView.attributedText];
NSRange range = NSMakeRange(0, attributedString.length - 1);
// Walk the string's attributes
[attributedString enumerateAttributesInRange:range options:NSAttributedStringEnumerationReverse usingBlock:
^(NSDictionary *attributes, NSRange range, BOOL *stop) {
// Find the font descriptor which is based on the old font size change
NSMutableDictionary *mutableAttributes = [NSMutableDictionary dictionaryWithDictionary:attributes];
UIFont *font = mutableAttributes[#"NSFont"];
UIFontDescriptor *fontDescriptor = font.fontDescriptor;
// Get the text style and get a new font descriptor based on the style and update font size
id styleAttribute = [fontDescriptor objectForKey:UIFontDescriptorTextStyleAttribute];
UIFontDescriptor *newFontDescriptor = [UIFontDescriptor preferredFontDescriptorWithTextStyle:styleAttribute];
// Get the new font from the new font descriptor and update the font attribute over the range
UIFont *newFont = [UIFont fontWithDescriptor:newFontDescriptor size:0.0];
[attributedString addAttribute:NSFontAttributeName value:newFont range:range];
}];
textView.attributedText = attributedString;
}
I have a method that gives me the perfect size for a UITextView given a length of string (with the corresponding correct font size) :
- (NSInteger) heightOfLabel:(NSString*) string {
CGSize maximumLabelSize = CGSizeMake([[UIScreen mainScreen] bounds].size.width - 40, FLT_MAX);
CGSize expectedLabelSize = [[NSString stringTrimmedForLeadingAndTrailingWhiteSpacesFromString:string]
sizeWithFont:[UIFont systemFontOfSize:15]
constrainedToSize:maximumLabelSize
lineBreakMode:NSLineBreakByWordWrapping];
return expectedLabelSize.height + 5;
}
In fact, it still gives me a perfect fit, even in iOS7. Although now it comes up with a warning method that says I shouldn't use 'sizeWithFont:contrainedToSize:lineBreakMode'.
It now says I should be using -boundingRectWithSize:options:attributes:context:
This method isn't new to iOS7 and therefore i figure that it is okay to ask it on stack overflow, rather than going across to the official apple developers forum.
I have three questions:
1) Because it is deprecated, does that mean I should definitely replace it, despite it still working?
2) I have tried many different boundingRectWithSize: methods, with various variables but it is never perfect, it always seems to be slightly out (as many stackoverflow questions point out) - is there a perfect replacement with this none-deprecated method that does exactly the same as my previous method with as minimal hassle?
3) why remove this method? Is it because of the overlap with this other method?
After an hour of trial error I managed to make it work:
CGSize maximumLabelSize = CGSizeMake(tableView.width, MAXFLOAT);
NSStringDrawingOptions options = NSStringDrawingTruncatesLastVisibleLine |
NSStringDrawingUsesLineFragmentOrigin;
NSDictionary *attr = #{NSFontAttributeName: [UIFont systemFontOfSize:15]};
CGRect labelBounds = [string boundingRectWithSize:maximumLabelSize
options:options
attributes:attr
context:nil];
Update:
As Mr. T mentions in answer below : In iOS 7 and later, this method returns fractional sizes (in the size component of the returned CGRect); to use a returned size to size views, you must use raise its value to the nearest higher integer using the ceil function. ceilf function is recommended to use.
CGFloat height = ceilf(labelBounds.size.height);
I believe the function was deprecated because that series of NSString+UIKit functions were based on the UIStringDrawing library, which wasn't thread safe. If you tried to run them not on the main thread (like any other UIKit functionality), you'll get unpredictable behaviors. In particular, if you ran the function on multiple threads simultaneously, it'll probably crash your app. This is why in iOS 6, they introduced a the boundingRectWithSize:... method for NSAttributedStrings. This was built on top of the NSStringDrawing libraries and is thread safe.
If you look at the new NSString boundingRectWithSize:... function, it asks for an attributes array in the same manner as a NSAttributeString. If I had to guess, this new NSString function in iOS 7 is merely a wrapper for the NSAttributeString function from iOS 6.
On that note, if you were only supporting iOS 6 and iOS 7, then I would definitely change all of your NSString's sizeWithFont:... to the NSAttributeString's boundingRectWithSize. It'll save you a lot of headache if you happen to have a weird multi-threading corner case! Here's how I converted NSString's sizeWithFont:constrainedToSize::
What used to be:
NSString *text = ...;
CGFloat width = ...;
UIFont *font = ...;
CGSize size = [text sizeWithFont:font
constrainedToSize:CGSizeMake(width, CGFLOAT_MAX)];
Can be replaced with:
NSString *text = ...;
CGFloat width = ...;
UIFont *font = ...;
NSAttributedString *attributedText =
[[NSAttributedString alloc]
initWithString:text
attributes:#
{
NSFontAttributeName: font
}];
CGRect rect = [attributedText boundingRectWithSize:CGSizeMake(width, CGFLOAT_MAX)
options:NSStringDrawingUsesLineFragmentOrigin
context:nil];
CGSize size = rect.size;
Please note the documentation mentions:
In iOS 7 and later, this method returns fractional sizes (in the size
component of the returned CGRect); to use a returned size to size
views, you must use raise its value to the nearest higher integer
using the ceil function.
So to pull out the calculated height or width to be used for sizing views, I would use:
CGFloat height = ceilf(size.height);
CGFloat width = ceilf(size.width);
For linebreak issue:
- (CGFloat)heightNeededForText:(NSString *)text withFont:(UIFont *)font width:(CGFloat)width lineBreakMode:(NSLineBreakMode)lineBreakMode {
NSMutableParagraphStyle * paragraphStyle = [[NSMutableParagraphStyle alloc] init];
paragraphStyle.lineBreakMode = lineBreakMode;
CGSize size = [text boundingRectWithSize:CGSizeMake(width, CGFLOAT_MAX)
options:(NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading)
attributes:#{ NSFontAttributeName: font, NSParagraphStyleAttributeName: paragraphStyle }
context:nil].size;
return ceilf(size.height);
}
Swift version of the Alexander of Norway's answer...
func heightNeededForText(text: NSString, withFont font: UIFont, width: CGFloat, lineBreakMode:NSLineBreakMode) -> CGFloat {
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineBreakMode = lineBreakMode
let size: CGSize = text.boundingRectWithSize(CGSizeMake(width, CGFloat.max), options: [.UsesLineFragmentOrigin, .UsesFontLeading], attributes: [ NSFontAttributeName: font, NSParagraphStyleAttributeName: paragraphStyle], context: nil).size//text boundingRectWithSize:CGSizeMake(width, CGFLOAT_MA
return ceil(size.height);
}
In the code where you want to get the height just call the method like below...
let size = self.heightNeededForText(text as NSString, withFont: UIFont.systemFontOfSize(15.0), width: scrollView.frame.size.width - 20, lineBreakMode: NSLineBreakMode.ByWordWrapping) //Can edit the font size and LinebreakMode
I have the below code in my app
NSMutableAttributedString * string = [[NSMutableAttributedString alloc] initWithString:self.myDisplayTxt];
[string addAttribute:(NSString*)kCTForegroundColorAttributeName
value:(id)[[UIColor redColor] CGColor]
range:NSMakeRange(0,5)];
self.myTextView.text = string;
When assigning the NSMutableAttributedString to UITextView I get the following error:
Incompatible objective c types struct NSMutableAttributedString
expected struct nsstring
So please let me know, how can I display the NSMutableAttributedString in UITextView.
You can try to use some library to do that. As omz wrote, the UITextView does not unfortunatelly support the NSAttributedString.
Maybe this one can help you: https://github.com/enormego/EGOTextView
They say about this library the following:
UITextView replacement with additional support for NSAttributedString.
UPDATE: Based on your clarification in the comment for omz's answer, you can look here:
Underline text inside uitextview
UPDATE 2: In iOS 6 you can use the NSAttributedString out of the box. For example like this:
UIColor *_red=[UIColor redColor];
UIFont *font=[UIFont fontWithName:#"Helvetica-Bold" size:72.0f];
[attString addAttribute:NSFontAttributeName value:font range:NSMakeRange(0, _stringLength)];
[attString addAttribute:NSStrokeColorAttributeName value:_red range:NSMakeRange(0, _stringLength)];
[attString addAttribute:NSStrokeWidthAttributeName value:[NSNumber numberWithFloat:-3.0] range:NSMakeRange(0, _stringLength)];
You can't, UITextView doesn't support attributed strings. If you really only want to use attributed strings to set the foreground color (as in your example), you could achieve the same by setting the text view's textColor property.
You should assign attributed string via UITextView.attributedText property. UITextView.text accepts NSString or plain text only.
textView.attributedText = attributedString;
See documentation:
https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITextView_Class/index.html#//apple_ref/occ/instp/UITextView/attributedText
I've got an NSTask (with an NSPipe set up) running in the background and I want to output the contents, as they're coming in, in an NSTextView (output).
The code I'm using is :
NSMutableAttributedString* str = [[NSMutableAttributedString alloc] initWithString:s];
//[str addAttribute:NSForegroundColorAttributeName value:[NSColor whiteColor] range:NSMakeRange(0, [str length])];
[[output textStorage] appendAttributedString:str];
[output scrollRangeToVisible:NSMakeRange([[output string] length], 0)];
Issues :
When there is a lot of data appending, the view seems like "flashing"... and not working properly.
Given that the NSTextView is on a Sheet, NO CONTENTS seem to be appearing when the mouse pointer is elsewhere other than hovering above the NSTextView
Why is that, although I've set the color/insertion color/etc of the NSTextView, this doesn't seem to apply to newly inserted text?
What's the suggested way of appending (+scrolling) on an NSTextView?
Thanks!
Remember that user interface elements, and this includes NSTextView, do their magic on the main thread. If you're attempting to add information to the text view, that's where you'd best be doing it. Here's how:
[[output textStorage] performSelectorOnMainThread:#selector(appendAttributedString:)
withObject:str
waitUntilDone:YES];
I'd address your third point, but frankly, that's a thing of which I'm still very much a student.
To address your fourth point, it would appear you've got that figured out; just combine your append and scroll actions. But just like changing the contents of textStorage, you want to be sure you're doing this on the main thread. Since -scrollRangeToVisible: doesn't take an object for its argument, you have to do this a bit differently:
dispatch_async(dispatch_get_main_queue(), ^{
[output scrollRangeToVisible:NSMakeRange([[output string] length], 0)];
});
My first example notwithstanding, you could place your call to -appendAttributedString: inside that block as well:
dispatch_async(dispatch_get_main_queue(), ^{
[[output textStorage] appendAttributedString:str];
[output scrollRangeToVisible:NSMakeRange([[output string] length], 0)];
});
Regarding the recommended way of appending to the NSTextView: You're doing quite well with appendAttributedString:, but it's recommended to shield it inside shouldChangeTextInRange, then a beginEditing, appendAttributedString, and finally endEditing:
textStorage = [textView textStorage];
if([textView shouldChangeTextInRange:range replacementString:string])
{
[textStorage beginEditing];
[textStorage replaceCharactersInRange:range withAttributedString:attrStr];
// or if you've already set up the attributes (see below)...
// [textStorage replaceCharactersInRange:range withString:str];
[textStorage endEditing];
}
I'd strongly suggest replacing scrollRangeToVisible: by scrollToPoint:, as scrollRangeToVisible: will cause a lot of flickering and it will also gradually become slower as you move 'down the range'.
A quick-and-dirty way could be something like this:
- (void)scrollToBottom
{
NSPoint pt;
id scrollView;
id clipView;
pt.x = 0;
pt.y = 100000000000.0;
scrollView = [self enclosingScrollView];
clipView = [scrollView contentView];
pt = [clipView constrainScrollPoint:pt];
[clipView scrollToPoint:pt];
[scrollView reflectScrolledClipView:clipView];
}
I let constrainScrollPoint do all the calculation work.
I do this, because my calculations failed anyway (those suggested by Apple and others, that used visRect/docRect coordinates, produced unreliable results).
reflectScrolledClipView is also important; it updates the scroll bar so it has the correct proportion and position.
You might also find it interesting to know when scrolling has occurred. If so, subscribe to both NSViewBoundsDidChangeNotification and NSViewFrameDidChangeNotification. When one of them occurs, the scroll bar position most likely changed (investigate [textView visibleRect] and [textView bounds]).
I see you also have trouble with the text-attributes. So did I for a long time.
I found that appending an attributed string would help quite a lot, but it still wasn't enough for the text being typed.
..Then I found out about typingAttributes.
When setting up your NSTextView, for instance in an -awakeFromNib, you can pick what you like from the following...
NSMutableParagraphStyle *paragraphStyle;
float characterWidth;
NSFont *font;
uint32_t tabWidth;
NSMutableDictionary *typingAttributes;
tabWidth = 4;
font = [NSFont fontWithName:#"Monaco" size:9.0];
paragraphStyle = [[textView defaultParagraphStyle] mutableCopy];
if(NULL == paragraphStyle)
{
paragraphStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
// or maybe:
// paragraphStyle = [NSParagraphStyle new];
}
characterWidth = [[font screenFontWithRenderingMode:NSFontDefaultRenderingMode] advancementForGlyph:(NSGlyph)' '].width;
[paragraphStyle setDefaultTabInterval:(characterWidth * (float) tabWidth];
[paragraphStyle setTabStops:[NSArray array]];
typingAttributes = [[textView typingAttributes] mutableCopy];
if(NULL == typingAttributes)
{
typingAttributes = [NSMutableDictionary new];
}
[typingAttributes setObject:paragraphStyle forKey:NSParagraphStyleAttributeName];
[typingAttributes setObject:font forKey:NSFontAttributeName];
[textView setTypingAttributes:attributes];
...It's way more than you probably need, but it shows how you can set the font, the tab width and the typing attributes.
NSForegroundColorAttributeName might also be interesting for you (as well as some other attributes, type NSForegroundColorAttributeName in Xcode and option-double-click on it, then you'll see some more attributes (you can command-double-click as well; this takes you to the definition in the header file).