UILabel get CGRect for substring of text - objective-c

My objective is simple: From a UILabel get the rect of a substring in the text of the label. From extensive searching there doesn't appear to be anything built in to handle this. Other people have asked similar questions, but none have been answered fully here on Stackoverflow.
// Something like this perhaps (added as a category)
CGRect rect = [myLabel rectForRange:NSMakeRange(3, 5)];
An example of what it could be used for (just to clarify what I'm searching for):

This was mostly needed for one character rects, so I went crazy and wrote some code that could accurately calculate the rect for the most basic UILabel. Standard UILineBreakMode and text alignment.
I was hoping that if I release it to the public people code contribute to it and improve it, especially since I don't know so much about text rendering!
The code:
https://gist.github.com/1278483

You're right: this isn't a built-in part of UILabel.
If you really need this, subclass UILabel, use CoreText to implement -drawRect:, and you'll be able to compute the rect for a range via CTLineGetOffsetForStringIndex(), CTFrameGetLineOrigins(), and CTLineGetTypographicBounds(). If the text in the range gets laid out so as to cross a line break, this will get complicated; you'll likely want multiple rects.

Related

NSView -alignmentRectForFrame: not respecting flipped coordinates?

HEADS-UP: I write way too much stuff. For the folks who don't want to read a bunch of whining, feel free to skip to the TL;DR at the bottom of the page. Thanks!
For the last few weeks, I've been subclassing all of the standard Aqua controls to make them look good on dark backgrounds, panels, and windows. I'm currently pretty far in, and for the most part, things are lookin' good. However, I keep running into the same problems over and over again. They're almost always math-related because I was big screwup in school. (I was that kid who would dismissively ask, "When will I ever use a stem-and-leaf plot in the real world? Ha!" In other words, I bought my TI-86 for Block Dude and Drug Wars. Anyway, I digress …
So the problem that keeps cropping up is my ineptitude when it comes to working with multiple coordinate systems. Specifically, working with Auto Layout in flipped coordinate systems. By default, NSView objects use Cartesian coordinates. However, most — if not all — of the NSControl classes flip their coordinates for drawing. Knowing that, this is what I've been doing:
- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView
{
// -isHUDPanel is just a method I added to NSWindow via category.
if ( controlView.window.isHUDPanel ) {
[self drawCustomWithFrame:cellFrame inView:controlView];
} else {
[super drawWithFrame:cellFrame inView:controlView];
}
}
If the control's window has the NSHUDWindowMask bit set, the -drawWithFrame:inView: method kicks it over to my custom method:
- (void)drawCustomWithFrame:(NSRect)cellFrame inView:(NSView *)controlView
{
NSRect drawingRect = [controlView alignmentRectForFrame:cellFrame];
// ...
// Where the magic happens
// ...
}
Auto Layout still baffles me, so I'm not even sure I'm using -alignmentRectForFrame: in the proper context, but it makes sense to me to use it to align stuff when drawing, so that's what I've been doing. Anyway, the problem I've been seeing sometimes when comparing my control side-by-side to a native control is that mine is 1–2 points "off" on one axis or another. Sometimes they align perfectly. Sometimes one axis is great and the other is whacky. It's a crapshoot. Then there are other times when the controls' frames will align just right, but the baselines of the controls' text will be a few points off. It's really maddening.
I finally decided to try some stuff, and it seems as though my alignment troubles may be stemming from the way NSEdgeInsets work. For example, let's say my control's bounds look like this:
NSRect: {{0, 0}, {150, 22}}; // (aka "cellFrame" in the drawing methods)
In my -drawWithFrame:inView: implementation, I first get the control's alignment rectangle by calling [controlView alignmentRectForFrame:cellFrame], which returns, let's say …
NSRect: {{2, 3}, {146, 19}}
At first glance, it seems as though the edges on the x-axis are inset by 2.0, and the y origin is 3.0. With that quick math out of the way, I now know everything I need to get started building NSBezierPath objects and going to work. But wait … Is that y origin in the control's coordinate system (flipped) or the default coordinate system (not flipped)? Surely it's in the control's, right? Well, I decided to test this out by manually setting the alignmentRect via the control's -alignmentRectInsets like so:
NSEdgeInsets insets = self.controlView.alignmentRectInsets;
NSRect bounds = self.controlView.bounds;
NSRect alignmentRect = bounds;
alignmentRect.origin.x += insets.left;
alignmentRect.origin.y += (self.controlView.isFlipped) ? insets.top : insets.bottom;
alignmentRect.size.width -= insets.left + insets.right;
alignmentRect.size.height -= insets.top + insets.bottom;
When manually creating the alignmentRect myself, my controls lined up with the native ones! I just don't get it though. Why aren't the controls' -alignmentRectForFrame: methods returning their alignmentRects whilst respecting flippedness of the view? I'm not even sure if they all do or not, but from the two controls I've tested this little trick on, the alignment has been on the money.
I've searched all over the web for some insight on this, but nobody seems to have the weird geometry problems I have. I always make these questions way too long, too, and I apologize. I just can't explain my problem when I don't exactly know my problem. :-(
TL;DR: How do I get the proper alignment rectangle for drawing in an NSControl subclass? Is there even a "standard" way? And finally, how does -cellSize, -cellSizeForBounds, -baselineOffsetFromBottom, and -intrinsicContentSize all fit together in the grand scheme of things? And final question: Auto Layout … WTF?

Draw a CGRect with Cocos2d

I'm using CGRect's for hitboxes, and my collisions seem to be a bit off. I want to quickly see where my hitboxes actually are.
I tried a bunch of different approaches but most of them seem to be outdated, or just didn't work for me.
I tried this already and a bunch of similar approaches.
What is the simplest way to show the borders of a CGRect?
With cocos2d 2.0, in ccConfig.h there is a CC_SPRITE_DEBUG_DRAW symbol. If you set that to 1, the box will be drawn during the visit cycle.
If CC_SPRITE_DEBUG_DRAW, as YvesLeBorg suggested, doesn't suit you, you can override draw method in you layer or nodes and draw in that method using helper functions from CCDrawingPrimitives.h. Don't forget to call [super draw].

NSTextField accessibility - How to provide alternate text for voiceover

It will probably be obvious, but I have never done any work with NSAccessibility before so I'm assuming what I'm asking is something simple I've overlooking.
I have an NSTextField displaying a duration like this, 15:39. This text field is a subview of an NSTableCellView in a view-based NSTableView.
When VoiceOver is enabled, it currently reads off, "one five, three nine", which seems completely useless. Instead, I want it to say "Duration is 15 minutes, 39 seconds."
I can produce the desired string, but I cannot figure out which accessibility attributes I have to set to make this happen.
I've tried setting the accessibility description in IB, which has no effect (whether setting it on the NSTextField or NSTextFieldCell).
I've also tried overriding accessibilityValue: and accessibilityAttributeValue:forParameter: in order to provide custom attribute values for:
NSAccessibilityNumberOfCharactersAttribute
NSAccessibilityStringForRangeParameterizedAttribute
NSAccessibilityAttributedStringForRangeParameterizedAttribute
NSAccessibilityStringForRangeParameterizedAttribute.
This seemed to be the right track since that does allow me to replace what is read aloud by voice over, however, providing any NSRange for NSAccessibilityVisibleCharacterRangeAttribute that doesn't match the length of the "15:39" string causes voice over to completely skip this field when reading off the NSTableCellView's contents. So, the best I've been able to do is get Voice over to say "Durat" instead of reading off "15:39" :(
Everything I've tried, I've tried on NSTextField and NSTextFieldCell.
Ideally, I'd prefer to do what I'd do in iOS and just set the accessibilityLabel of the NSTableCellView, but I see no reasonable way of doing this in AppKit. Hopefully I'm just missing something.
I was able to achieve this simply by setting accessibilityValueDescription on the NSTextField. This method is part of the new Accessibility API on OS X 10.10 and higher. With the older API, you may be able to use kAXValueDescriptionAttribute to achieve the same thing.
The solution for overriding the text read by Voice Over was much simpler than I thought. All I had to do was override the value returned for NSAccessibilityAttributedStringForRangeParameterizedAttribute:
// The displayed text for this text field is "45m".
// The voice over for this field incorrectly reads "forty five meters" instead of "forty five minutes".
// The following forces it to read "Duration forty five minutes"
-(id)accessibilityAttributeValue:(NSString *)attribute forParameter:(id)parameter {
return ([attribute isEqualToString:NSAccessibilityAttributedStringForRangeParameterizedAttribute])
? [[NSAttributedString alloc] initWithString:#"Duration: 45min"];
: [super accessibilityAttributeValue:attribute forParameter:parameter];
}
I believe I've answered my own question, at least enough to control what's read off when clicking a table cell view.
By overriding accessibilityIsIgnored to return "NO" in my NSTableCellView sub-class I was able to specify exactly what I wanted to be read off for the table cell by overriding the NSAccessibilityTitleAttribute for the cell. I had not tried this before b/c I had misunderstood the purpose of the accessibilityIsIgnored selector.
From the documentation for accessibilityIsIgnored:
When asking for an object’s children, ignored children should not be included; instead, the ignored children should be replaced by their own unignored children.
I'd like to be able to control exactly what is read off for the individual NSTextFields in the future, but controlling what is read off for an entire NSTableCellView is actually ideal for my particular situation.

How to insert a NSButton into a NSTextView? (inline)

I have a NSTextView and a custom NSButton. What I want to do is to insert that button (60x16 in size) to the end of the NSTextView.
How can I do something like that? I've been trying to search around on how to do this but I'm not getting anywhere.
Where should I begin? Thanks
I believe this question is pretty similar to yours:
Buttons inside an NSTextView / NSTextStorage
Quote from the question:
how do I get an NSButton to appear inside the text and react to
clicks?
Note that the issue is not fully solved there, but it seems the OP got a good head start. Hopefully you can take some clues from the discussion.
Here is one answer:
NSTextAttachment holds the contents of an attachment; it is the value
of the NSAttachmentAttributeName attribute for the
NSAttachmentCharacter in the attributed string. The contents are
usually given by an NSFileWrapper, but this is not required; you can
create an empty NSTextAttachment with a nil file wrapper.
NSTextAttachmentCell handles display and user interaction for the
attachment. By default an NSTextAttachment will create an
NSTextAttachmentCell to display itself, depending on the contents of
the attachment; in the generic case this will just be an image cell
displaying an icon.
If you wish, however, you can supply a custom NSTextAttachmentCell for
your attachment. It need not be an member of the class
NSTextAttachmentCell; it only needs to conform to the
NSTextAttachmentCell protocol. Actually, even that is not strictly
necessary; it only needs to implement a few of the basic methods for
sizing and drawing. Most cells already do this.
You will, however, need to deal with mouse events yourself. The
methods you'll probably want to implement would be
wantsToTrackMouseForEvent:inRect:ofView:atCharacterIndex: and
trackMouse:inRect:ofView:atCharacterIndex:. The character index here
should let you determine which portion of the text is relevant.

How does Apple use bold and normal fonts for a Contacts' name in ABPeoplePickerNavigationController

Short and sweet:My end goal is to, using one UILabel with two words, display one word in bold, and the other in normal font. All solutions I've found are over my head! :(
More detail:
I asked a question the other day that didn't give me the answer I was hoping for, and now it looks like I need to recreate ABPeoplePickerNavigationController using a UITableViewController and contents from ABAddressBook. If you read my previous question and can come up with a better solution, awesome! If not...
When I recreate the PeoplePicker, I really want to mimic Apple's default implementation exactly, especially how they use both bold and normal fonts for a contacts' name in the same label.
Now, I know this question has been asked a lot, on SO and elsewhere. Many people have had this need, and the solutions vary:
use a webview
use the UIStringDrawing methods
subclass UILabel and override drawrect
delve into CoreText
use the ThreeTwenty library
use two uilabels, create one as bold, one as normal, and place them next to each other (would require me to then subclass UITableViewCell in order to place two labels
Solutions #2, #3, and #4 seem the most robust, and according to what I've read in other questions, it's how Apple is actually doing it. Unfortunately, these techniques are a little over my head at the moment, and I'm having a hard time following the limited amount of details I'm finding on the web. I haven't really done anything with explicit drawing yet.
Has anybody implemented this yet themselves? Any code samples you can provide to help me get the hang of these more advanced techniques?
Thanks!
There's a 6th solution. Use 2 UILabels. Create one, in bold, with the bold text, and ask it to size to fit. Then once you've laid it out, you can create the second and place it right next to the first.
You should read the "Table View Programming Guide for iOS" there is a section titled "Subclassing UITableViewCell" it does exactly what you want (i.e. draw strings of different properties).
So, just to round this out, I ended up finding a 7th option. :)
I went with OHAttributedLabel. I still wasn't able to wrap my head around UIStringDrawing methods or overriding drawRect in a subclassed UILabel; I think those would have been the cleanest solution. However, OHAttributedLabel seems solid, is super lightweight, and does exactly what I need it to do.
Good luck! If anyone finds more solutions, or feels like they want to explain UIStringDrawing or or drawRect in more detail, feel free to post!
Update:
I've been using Matt Thompson's TTTAttributedLabel and have been really happy with it.
https://github.com/mattt/TTTAttributedLabel