NSMutableAttributedString - NSForegroundColorAttributeName - iphone

I am trying to set the color of all the ranges in my array but I am getting this error. I don't understand why. the ranges are all valid. I even tried manually inserting a range to test it. Thank You.
CGContextSetFillColorWithColor: invalid context 0x0
NSMutableAttributedString * string = [[NSMutableAttributedString alloc] initWithString:tv.text];
for (NSString * s in array) {
[string addAttribute:NSForegroundColorAttributeName value:[UIColor redColor] range:NSRangeFromString(s)];
}
CATextLayer *textlayer = [[CATextLayer alloc]init];
textlayer.frame = CGRectMake(0, 0, 320, 480);
[self.view.layer addSublayer:textlayer];
textlayer.string = #"aString"; //works
textlayer.string = string; //does not work
tv.text = #"";

Is the code example the exact same code as you are trying to build? Im quite sure NSForegroundColorAttributeName is only available in Mac OS X SDK and iOS 6.0 and later so the example code should not even compile.
What you want instead is probably kCTForegroundColorAttributeName and pass a CGColorRef instead of a NSColor.
[string addAttribute:(id)kCTForegroundColorAttributeName
value:(id)[UIColor redColor].CGColor
range:NSRangeFromString(s)];
But im not sure if this really is the cause of the invalid context error.

Your context is wrong, not your range. You're trying to set the color of something that doesn't have a color.

Related

UILabel auto size error on iOS 7

I'm making my app transition to iOS 7 and have this method (already modified for iOS 7, using boundingRectWithSize...):
+ (CGSize)messageSize:(NSString*)message {
NSDictionary *attributes = #{NSFontAttributeName : [UIFont fontWithName:#"Futura-Medium" size:13]};
CGRect frame = [message boundingRectWithSize:CGSizeMake([PTSMessagingCell maxTextWidth], CGFLOAT_MAX) options:NSStringDrawingUsesFontLeading attributes:attributes context:nil];
return frame.size;
}
I am getting this appearance:
The message UILabel is being cut. It feels like line spacing is too big. It tried many other answers I found but none of them work.
If someone knows how to help me, I appreciate! ;)
Thanks!
Try changing NSStringDrawingUsesFontLeading as your option to NSStringDrawingUsesLineFragmentOrigin.
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. Starting in iOS 6, the NSAttributedString's NSStringDrawing functions were introduced and they're threadsafe unlike the old NSString+UIKit methods we're used to (eg. sizeWithFont:..., etc), which were UIStringDrawing functions (and act unpredictably when you use them from a non-main thread. 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:(CGSize){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:(CGSize){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);
I think you are updating your label frame from either viewDidLoad or viewWillAppear, so it is not working.
if you will update frame of label from viewDidAppear method then you will get updated frame of label.
I am not sure why this is happened, I think it is iOS 7 bug.
Try this
+ (CGSize)messageSize:(NSString*)message {
CGSize nameSize = [message sizeWithFont:[UIFont fontWithName:#"Futura-Medium" size:13]
constrainedToSize:CGSizeMake(maxWidth, maxHeight) lineBreakMode:NSLineBreakByWordWrapping];
NSLog(#"width = %f, height = %f", nameSize.width, nameSize.height);
return nameSize;
}

respond to selector method for unsupported implementations

In my app I need to display under line text in a label so I used following code to display underlined text
NSMutableAttributedString *attributeString = [[NSMutableAttributedString alloc] initWithString:normalString];
[attributeString addAttribute:NSUnderlineStyleAttributeName
value:[NSNumber numberWithInt:1]
range:(NSRange){0,[attributeString length]}];
wesiteAddressLabel.attributedText = attributeString;
This method and some other implementations which works fine in iOS 6.1
But when I executed in iOS 5.1 and below, app gets crashed due to the reason,
[attributeString addAttribute:NSUnderlineStyleAttributeName
value:[NSNumber numberWithInt:1]
range:(NSRange){0,[attributeString length]}];
not supported in previous versions
So I want to use respondsToSelector: method to check if instance responds and implement another method for unsupported selector.
How I use this method?
As from the documentation:
attributedText The styled text displayed by the label.
#property(nonatomic,copy) NSAttributedString *attributedText
Discussion This property is nil by default. Assigning a new value to
this property also replaces the value of the text property with the
same string data, albeit without any formatting information. In
addition, assigning a new a value updates the values in the font,
textColor, and other style-related properties so that they reflect the
style information starting at location 0 in the attributed string.
Availability Available in iOS 6.0 and later. Declared In UILabel.h
You should check if the a specific UIView element is able to respond to the attributedText. In this case:
[wesiteAddressLabel respondsToSelector:#selector(attributedText)];
Should be enough
For previoes versions you have to draw an UIImageView Just below the Text by getting the with and Height of text in each line.
Or you can create a category of label by using DrawRect method.
- (void)drawRect:(CGRect)rect
{
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextSetRGBStrokeColor(ctx, 0.0f/255.0f, 0.0f/255.0f, 255.0f/255.0f, 1.0f); // Your underline color
CGContextSetLineWidth(ctx, 1.0f);
UIFont *font = [UIFont systemFontOfSize:16.0f];
CGSize constraintSize = CGSizeMake(MAXFLOAT, MAXFLOAT);
CGSize labelSize;
labelSize = [self.text sizeWithFont:font constrainedToSize:constraintSize lineBreakMode:UILineBreakModeWordWrap];
CGContextMoveToPoint(ctx, 0, self.bounds.size.height - 1);
CGContextAddLineToPoint(ctx, labelSize.width + 10, self.bounds.size.height - 1);
CGContextStrokePath(ctx);
[super drawRect:rect];
}

iOS 6 MFMailComposeViewController: Only support RGBA or the White color space, this method is a hack

really, really odd error
I have an app that was working fine in iOS5/.1, now I'm having a few transition problems with iOS6 but this one is confusing.
I have some code that launches a mail composer, and since iOS 6 it causes a crash with this error:
* Assertion failure in -[UICGColor encodeWithCoder:], /SourceCache/UIKit/UIKit-2372/UIColor.m:1191
2012-09-26 02:14:38.044 MyCQs Medical[2126:1b03] * Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Only support RGBA or the White color space, this method is a hack.'
Any suggestions? Through trial and error commenting out various lines it seems to be the alloc/init line that causes the error, though when all the lines are uncommented, all of the NSLogs are executed, including "present" which indicates that everything that should be called, has been. the app crashes before the mailcomposer is presented on screen, I'd really appreciate any advice here
if (indexPath.row == 3) {
if([MFMailComposeViewController canSendMail]){
mailComposer = [[MFMailComposeViewController alloc]init];
NSLog(#"Alloc, init");
mailComposer.mailComposeDelegate = self;
NSLog(#"Set delegate");
NSArray *toArray = [[NSArray alloc]initWithObjects:#"john#doe.com", nil];
NSLog(#"To array");
[mailComposer setToRecipients:toArray];
NSLog(#"To recipients");
[mailComposer setSubject:#"Message from a MyCQs user!"];
NSLog(#"Subject");
NSLog(#"About to present mail composer");
[[mailComposer navigationBar] setTintColor:[UIColor blackColor]];
[self presentModalViewController:mailComposer animated:YES];
NSLog(#"Present");
}
}
Ok only a partial solution but it's the best one I can think of without some fairly disruptive code changes.
For anyone else who gets this problem in future, I think it's a bug in iOS 6, MFMailComposeViewController crashes when you have set the UITableView separatorStyle to a colorWithPatternImage, but it works fine when you use a solid colour, so:
if ([[[UIDevice currentDevice] systemVersion] floatValue] > 5.2) {
NSLog(#"Using iOS 5x");
[[UITableView appearance]setSeparatorColor:[UIColor colorWithRed:142.0/255.0 green:130.0/255.0 blue:76.0/255.0 alpha:1.0]];
}
else {
NSLog(#"Using iOS 6x, Table view use pattern color");
[[UITableView appearance]setSeparatorColor:[UIColor colorWithPatternImage: [UIImage imageNamed:#"dotted-line2.png"]]];
}
Actually I believe this is a significant bug, did anybody file a radar, yet?
As it seems to me, the assertion throws whenever you use "colorWithPatternImage" ANYWHERE in your presenting view controller's appearance proxies.
I think what happens is that iOS tries to store the appearance of your App before switching to a separate service (which is what MFMailComposeViewController does, it's now a "remote view controller" that gets presented by your App but which is managed by another App/process), since the Mail App wants to determine the appearance itself so it changes things like tint colors etc.
More about remote view controller here, in case somebody is interested:
http://oleb.net/blog/2012/10/remote-view-controllers-in-ios-6/
This seems to fail. This has to be a bug, the image should be encodable.
Workarounds may be hard. I tried to exchange the patterned color for a plain one right before presenting the view controller but found you at least have to make sure it really gets redrawn on screen and gave up (I don't really needed the pattern).
My problem was also due to using colorWithPatternImage but I found that it seems to only cause an issue when used on elements that are shown on the MFMailCompserViewController e.g.:
[[UINavigationBar appearance] setTintColor:[UIColor colorWithPatternImage:[UIImage imageNamed:#"navbar"]]];
I've found that MFMailComposeViewController only crashes when colorWithPatternImage is used in [[UITableView appearance] statement. I've had no problem when I set the background and separatorColor in the table's viewDidLoad. The only problem is you need to do this for each table.
[self.tableView setBackgroundColor:[UIColor colorWithPatternImage:[UIImage imageNamed:#"image.png"]]];
[self.tableView setSeparatorColor:[UIColor colorWithPatternImage:[UIImage imageNamed:#"image.png"]]];
#coolio is right, it seems to happen when using a patterned color with any controller class. (At least UITabBarController and UITableViewController, apparently.)
I got around this by subclassing my tab bar controller and using appearanceWhenContainedIn:. That way you can still use your fancy texture with no need to subclass MFMailComposeViewController or sniff iOS versions, etc.
UIColor *tabBarBgPattern = [UIColor colorWithPatternImage:...];
[[UITabBar appearanceWhenContainedIn:[MyTabBarController class],
nil]
setBackgroundColor:tabBarBgPattern];
This thread gave me great clues on how to fix a similar problem in my App using ShareKit and attempting to email an image of a view. Now I only change the UISwitch appearance for versions less than 6.0.
This code gets called when my app starts up:
// Protect against IOS 6.0 BUG: 'NSInternalInconsistencyException', reason: 'Only support RGBA or the White color space, this method is a hack.'
if ([[[UIDevice currentDevice] systemVersion] floatValue] < 6.0) {
UIImage *patternImage = [UIImage imageNamed:#"solid-colors_blue.png"];
UIColor* switchColor = [UIColor colorWithPatternImage:patternImage];
[[UISwitch appearance] setOnTintColor: switchColor];
}
I'm facing the same issue using 'colorWithPatterImage' for a UIBarButtonItem inside the navBar of my tableViewController used to display the a modal view (I'm not using MFMailComposeViewController).
[[UIBarButtonItem appearance] setTintColor:[UIColor colorWithPatternImage:bgImage]];
I needed to replace it with a RGBa color;
My apps were crashing on iOS 7 when using colorWithPatternImage and trying to present an MFMailComposeViewController. I have to determine the color from a UIImage because the UIImage can be chosen by the user, and I want to "theme" my application based on the user's selected image.
The solution for me (albeit kind of hacky) is to grab the RGBA values of the origin (0,0) of the UIImage, then apply that as the background color instead of using colorWithPatternImage.
+ (UIColor*)getUIColorFromImageAtOrigin:(UIImage*)image
{
// First get the image into your data buffer
CGImageRef imageRef = [image CGImage];
NSUInteger width = CGImageGetWidth(imageRef);
NSUInteger height = CGImageGetHeight(imageRef);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
unsigned char *rawData = (unsigned char*) calloc(height * width * 4, sizeof(unsigned char));
NSUInteger bytesPerPixel = 4;
NSUInteger bytesPerRow = bytesPerPixel * width;
NSUInteger bitsPerComponent = 8;
CGContextRef context = CGBitmapContextCreate(rawData, width, height,
bitsPerComponent, bytesPerRow, colorSpace,
kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
CGColorSpaceRelease(colorSpace);
CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef);
CGContextRelease(context);
// Now your rawData contains the image data in the RGBA8888 pixel format.
int byteIndex = (bytesPerRow * 0) + 0 * bytesPerPixel;
CGFloat red = (rawData[byteIndex] * 1.0) / 255.0;
CGFloat green = (rawData[byteIndex + 1] * 1.0) / 255.0;
CGFloat blue = (rawData[byteIndex + 2] * 1.0) / 255.0;
CGFloat alpha = (rawData[byteIndex + 3] * 1.0) / 255.0;
UIColor *acolor = [UIColor colorWithRed:red green:green blue:blue alpha:alpha];
free(rawData);
return acolor;
}
Now if I use this UIColor as the background instead of colorWithPatternImage, I can share to my heart's content without encountering this weird exception.
The above method was adapted from a solution that I found here: https://stackoverflow.com/a/1262893/1103584
It's a bug of iOS 6 i report it and it was marked as dublicate. So, someone report that bug earlier.
For using pattern, you can use resizableImageWithCapInsets.
In Monotouch it seems like this:
var sba = UISearchBar.Appearance;
var img = UIImage.FromBundle("myResource.png");
if (UIDevice.CurrentDevice.CheckSystemVersion(6, 0)) {
img = img.CreateResizableImage(UIEdgeInsets.Zero, UIImageResizingMode.Tile);
sba.BackgroundColor = UIColor.LightGray;
sba.BackgroundImage = img;
} else {
sba.BackgroundImage = new UIImage();
sba.BackgroundColor = UIColor.FromPatternImage(img);
}

Use CATextLayer with CGContext

I need to draw text into a CGContext and want to use CATextLayer to set the text. Can anyone give me some steps as to how it'll be done. I've written some code for it, but don't know the correctness of it (since I'm new to iPhone Development).
Some basic questions :
1. How to obtain position and size of text for setting the CATextLayer frame
2. The text is somehow coming out inverted which I've fixed using CGAffineTransform, but its a hacky fix and I want to know the actual reason why the text is coming out inverted.
3. I also need to set the text border width and color for which I'm using NSAttributedString, but setting the text size (increasing or decreasing) does not reflect on the screen.
EDIT 1 :
I've updated the code as per your recommendations, but the text is not resizing even after using CTFontRef. Also, on the iPhone the text is getting truncated a little bit from the top. Any idea what's being set incorrectly? When I checked the values I'm getting from getTypographicBounds, they're all 0 (origin.x, origin.y, width and height).
The getTypographicBounds method :
width = CTLineGetTypographicBounds(m_line, &ascent, &descent, &leading);
// Return text bounds
return CGRectMake(0, 0, width, ascent + descent);
The modified code :
CATextLayer* text = [[CATextLayer alloc] init];
CGColorSpaceRef rgbColor = CGColorSpaceCreateDeviceRGB();
CGColorRef rgb = CGColorCreate(rgbColor, m_fill_color);
text.foregroundColor = rgb;
text.frame = CGRectMake([self getTypographicBounds].origin.x, [self getTypographicBounds].origin.y, [self getTypographicBounds].size.width + 2*m_padding, [self getTypographicBounds].size.height + 2*m_padding);
text.wrapped = YES;
NSMutableDictionary *stringAttributes = [NSMutableDictionary dictionary];
CTFontRef aCFFont = CTFontCreateWithName ((CFStringRef)m_font_name, 30.0, NULL);
// Set a negative width so we get both stroke and fill to show
[stringAttributes setObject: (NSObject*)aCFFont forKey: (id)kCTFontNameAttribute];
[stringAttributes setObject: [NSNumber numberWithFloat: -3 ] forKey: (id)kCTStrokeWidthAttributeName];
[stringAttributes setObject: [UIColor whiteColor] forKey: (id)kCTStrokeColorAttributeName];
NSAttributedString *stringToDraw = [[NSAttributedString alloc] initWithString:m_text
attributes:stringAttributes];
text.string = (NSString*)stringToDraw;
[text drawInContext:ctx];
EDIT 2 : The wiki example uses CTLine, I'm using CATextLayer. Reason :: CATextLayer lets me modify text during runtime to show Chinese, Japanese, Hindi, Russian and all languages with different scripts. Also, CATextLayer seems to render with more clarity than CoreText.
The issue I'm facing right now is that the text being displayed by [text drawInContext:ctx] line is truncating from the top. I checked the frame and text font size. Font size is 16 px and frame height is 17.768, so I don't know why it's truncating.
// Ensure CTLine is up-to-date
if (m_is_dirty)
[self recomputeLine];
// Get text metrics
CGFloat width;
CGFloat ascent;
CGFloat descent;
CGFloat leading;
width = CTLineGetTypographicBounds(m_line, &ascent, &descent, &leading);
// Position text so that top of text aligns with top of layer
CGContextSetTextMatrix(ctx, CGAffineTransformIdentity );
// Setup text drawing style
CGContextSetRGBFillColor (ctx, m_fill_color [0], m_fill_color [1], m_fill_color [2], 1.0);
CGContextSetRGBStrokeColor (ctx, m_stroke_color[0], m_stroke_color[1], m_stroke_color[2], 1.0);
CGContextSetFont (ctx, m_font );
CGContextSetFontSize (ctx, m_font_size );
CGContextSetLineWidth (ctx, m_stroke_width);
CATextLayer* text = [[CATextLayer alloc] init];
CGColorSpaceRef rgbColor = CGColorSpaceCreateDeviceRGB();
CGColorRef rgb = CGColorCreate(rgbColor, m_fill_color);
text.foregroundColor = rgb;
text.fontSize = m_font_size;
text.font = m_font;
text.frame = CGRectMake([self getTypographicBounds].origin.x, [self getTypographicBounds].origin.y, [self getTypographicBounds].size.width, [self getTypographicBounds].size.height);
text.wrapped = YES;
NSMutableDictionary *stringAttributes = [NSMutableDictionary dictionary];
// Set a negative width so we get both stroke and fill to show
[stringAttributes setObject: [UIFont fontWithName:m_font_name size:m_font_size] forKey: (id)kCTFontNameAttribute];
[stringAttributes setObject: [NSNumber numberWithFloat: -3 ] forKey: (id)kCTStrokeWidthAttributeName];
[stringAttributes setObject: [UIColor whiteColor] forKey: (id)kCTStrokeColorAttributeName];
NSAttributedString *stringToDraw = [[NSAttributedString alloc] initWithString:m_text
attributes:stringAttributes];
text.string = (NSString*)stringToDraw;
[text drawInContext:ctx];
Thanks in advance
Nick
It is because of the flipped coordinate space used for CGContexts. For a full explanation (with diagrams) see here.
EDIT:
To control the position translate your context before drawing the text:
CGContextTranslateCTM(context, x, y);
EDIT 2:
Looks like you need to use a CTFontRef, not a UIFont. For an example, see here (see post by Gordon Apple Wed Jun 23 10:40:23 2010).
EDIT 3:
Unless you specifically need to use NSAttributedString, I would recommend using the much simpler NSString drawInRect:withFont:lineBreakMode:alignment: method. It creates a CATextLayer for you, but is much simpler to use:
UIFont *font = [UIFont fontWithName:m_font_name size:30.f];
[m_text drawInRect:text.frame withFont:font lineBreakMode:UILineBreakModeWordWrap alignment:UITextAlignmentLeft];
EDIT 4:
Try setting your dictionary like this:
CTFontRef ctFont = CTFontCreateWithName((__bridge CFStringRef)fontName, pointSize,NULL);
CGColorRef color = textColor.CGColor;
NSDictionary *attributesDict = [NSDictionary dictionaryWithObjectsAndKeys:
(__bridge id)ctFont, (id)kCTFontAttributeName,
color, (id)kCTForegroundColorAttributeName,nil];

NSString sizeWithFont: returning inconsistent results? known bug?

I'm trying to create a simple custom UIView wich contain a string drawn with a single font, but where the first character is slightly larger.
I thought this would be easily implemented with two UILabel:s placed next to eachother.
I use NSString sizeWithFont to measure my string to be able to lay it out correctly.
But I noticed that the font baseline in the returned rectangle varies with +/- 1 pixel depending on the font size I set.
Here is my code:
NSString* ctxt = [text substringToIndex:1];
NSString* ttxt = [text substringFromIndex:1];
CGSize sz = [ctxt sizeWithFont: cfont ];
clbl = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, sz.width, sz.height)];
clbl.text = ctxt;
clbl.font = cfont;
clbl.backgroundColor = [UIColor clearColor];
[contentView addSubview:clbl];
CGSize sz2 = [ttxt sizeWithFont: tfont];
tlbl = [[UILabel alloc] initWithFrame:CGRectMake(sz.width, (sz.height - sz2.height), sz2.width, sz2.height)];
tlbl.text = ttxt;
tlbl.font = tfont;
tlbl.backgroundColor = [UIColor clearColor];
[contentView addSubview:tlbl];
If I use 12.0 and 14.0 as sizes, it works fine.
But if I instead use 13.0 and 15.0, then the first character is 1 pixel too high.
Is this a known problem?
Any suggestions how to work around it?
Creating a UIWebView with a CSS and HTML page seems way overkill for this. and more work to handle dynamic strings. Is that what I'm expected to do?
Found the answer...
Ofcourse, I also have to check the descender value on the font, and compensate for that in the layout.
New rect for the second label is:
CGRectMake(sz.width, (sz.height - sz2.height) + floor(cfont.descender - tfont.descender), sz2.width, sz2.height)
floor() is to make sure it snaps to pixel position, or the font will look blurry