Is there a way to attach a UITextField inside inside the range of an NSAttributedString that is inside a UITextView?
I want to recreate something like this:
Where the user would be able to press the empty lines to type text.
You could calculate a frame for the UITextField like so:
- (CGRect)textFieldFrameForCharacterRange:(NSRange)charRange inTextView:(UITextView *)textView {
NSRange glyphRange = [textView.layoutManager glyphRangeForCharacterRange:charRange actualCharacterRange:NULL];
CGRect boundingRect = [textView.layoutManager boundingRectForGlyphRange:glyphRange inTextContainer:textView.textContainer];
return boundingRect;
}
and then add it as a subview of the UITextView. Determining a character range to pass to the above method can be easy too - just search for a run of underlines, or maybe a custom attribute using - enumerateAttribute:inRange:options:usingBlock:.
The catch is this won't work properly if the underlines are split into more than one line, but UITextField wouldn't fit that case anyway. So you need a way to make sure the underline remains unbroken. The U+2060 unicode character may help, see Prevent line break in a NSAttributedString for more. An NSTextAttachment with the underline represented as an image would also be guaranteed not to get word wrapped.
Alternatively, you could implement a UITextViewDelegate, or possibly even a UITextView subclass, that disallows selection and editing outside of the underline ranges. That way you can avoid the weirdness of UITextFields within UITextViews. But the drawback with this method is even if you deleted an underline character every time the user types a character, the user would still see the overall length of the underline fluctuating slightly.
Related
tl;dr: if I call NSAttributedString's enumerateAttribute:inRange:options:usingBlock: method, does the range argument to the block pass a glyph range or a character range?
I have a text view in my app that needs to behave like Notes.app in that when it's not in edit mode, it should show hyperlinks (and they should be tappable), and it should enter edit mode on tap. UITextView doesn't show hyperlinks when editable, but if it's not editable then it won't gain focus on tap by itself. Until iOS 11 this could be solved by using a UITapGestureRecognizer on the text view to trigger editing, but it seems something (maybe with drag and drop?) changed how gesture recognizers work with UITextView (rdar://33009324).
My new and improved solution is a custom UIGestureRecognizer that fires if it sees a tap that is not on a hyperlink, and making all other recognizers on the text view to require it to fail before firing. This is implemented by processing touches, getting their location in the text view, getting the character index for that point, enumerating the NSLinkAttributeName in the text view's textStorage, and checking if my character index overlaps with any of the hyperlinks I get back. If so, then the user tapped on a link, and this recognizer fails.
However, if the character index of the tap does intersect with a link, I need to make sure the tap actually intersects with the link's rect, because it could be that this link is the last thing in the text view's content and the touch is actually below the last line of text. I can do this by getting the rect for the range of the link and checking if it intersects with my touch point. I can use boundingRectForGlyphRange:inTextContainer: for this, but this leads to my question: is the range I have for the link a character range or a glyph range? Do I need to convert it via glyphRangeForCharacterRange:actualGlyphRange: first?
It returns a character range. NSAttributedString doesn't know anything directly about glyphs. Converting characters to glyphs can't happen until layout is performed, and NSAttributedString doesn't include layout information. For example, an NSAttributedString doesn't know how wide an area it will be drawn into, and without that, you can't know where line breaks will be, and without that you can't decide where to put ligatures.
NSAttributedString knows nothing glyphs, and yes, if you need to know where the glyphs are in a TextKit stack such as that of a UITextView, you ask the layout manager to convert for you.
I'm using this method to change color to few chars of my UITextView
+(void)changeColorToTextObject : (UITextView *)textView ToColor : (UIColor *)color FromLocation : (int)location WithLength : (int)length
{
NSMutableAttributedString *text = [[NSMutableAttributedString alloc] initWithAttributedString: textView.attributedText];
[text addAttribute:NSForegroundColorAttributeName value:color range:NSMakeRange(location, length)];
[textView setAttributedText: text];
}
It works great but after I change the specific range I want to be able to continue with the previous color and not with the new color that I just changed to.
How can I do it?
Actually, when working with attributed text, I found it best practice to set content first and style second (which actually originates from old typesetting rules).
In your case, I would first set the whole text on the UITextView, and then call the method changeColorToTextObject:ToColor:FromLocation:WithLength specifying only the portion of the text you want in a different color.
However, if you are not talking about static content, but rather something like a word processor app, where the user can change text color and continues to write afterwards, you need to think about how you want this app to work. Consider the following method:
- (void)highlightSelectedText {
UIColor *highlightColor = [UIColor redColor];
[self changeColorToTextObject:self.textView ToColor:highlightColor FromLocation:self.textView.selectedRange.location WithLength:self.textView.selectedRange.length];
}
This method allows you to highlight the specific part of the text, the user has currently selected:
If the user then continues typing, the new text will appear in red:
If instead, they put the focus at a place where font color is black, they will continue writing in black:
When you think about a word processor, that seems actually pretty familiar. And if that is good enough for you, you are good to go.
The only border case you need to worry about then is what you do, when there is no text selected.
However, if you need more control over the dynamic behavior of your UITextView (and if you are >iOS 7), you should consider using Text Kit, and in particular subclassing NSTextStorage. There is a introduction video to Text Kit from WWDC 2013, and a great tutorial by Ray Wenderlich.
Save your previous color into a 'UIColor' property before apply the attributedText to the UITextView, and assign it to the UITextView after using the 'changeColorToTextObject' method.
I'm working on editor with syntax highlighting. After each character typed i should parse the text and highlight it. The problem is that if i create NSAttributedString instance and do UITextViewinstance.attributedText = newAttrStr i have textViewDidChange: event and some reinits in UITextView and UITextStorage behind the scene.
How to apply attribute for text in UITextView without setting attributedText property?
Can i use NSMutableAttributeString for example and add/remove attributes (i believe it works if UITextView does not copy NSAttributedString instance only but it's #property(nonatomic, copy) NSAttributedString *attributedText)? Any another approach?
I've always been curious how instagram gets it's tags to layout fluidly and wanted to know what goes behind this functionality? To me, it seems like they're using UIButton's and just calculating placement of the buttons line by line? Is that right? Or is there a simpler method of doing it without having to manual calculate the placement of each tag line by line / or side by side given that multiple tag's width's fit together?
Or, are they using NSAttributedStrings?
Any ideas?
Here's an example of the layout I'm referring to.
Well, I'm currently working on NSAttributedString and UITextView to get all this working, and my current code can do it. So I'm going to explain a possible version of doing this.
Here are the tips and big steps, with iOS7:
NSAttributedString, in iOS7 has a new attribute : NSLinkAttributeName. You can add it to your NSAttributedString, and with a detection method (combining: how to make #key and #key clickable in IOS7 and Get word from tap in UITextView) you can manage it. That's for the general way of doing it. For previous version, you'll have to do a little more of work, but keeping this general idea.
But since, it only for hashtags, since you can detect the world behind the tap, you can do this: Detect what word has been tapped (see previous links), detect if this word has an hashtag, and if yes, you can do whatever action you want. You can play with a NSRegularExpression to know where are the hashtags, and know when to "color" them.
Here is a simplified way to do it using UITextView and NSAttributedString:
Use regular expressions to get character ranges for links.
Add link styling to the matched ranges.
Add custom attribute to range that references tap action to execute.
On touch, if index of character is inside a matched range, change the link formatting to highlight it and run the associated tap action.
How to get index of character at location in iOS7:
- (NSUInteger) getCharIndexAt: (CGPoint) location
{
NSLayoutManager *layoutManager = self.layoutManager;
NSUInteger characterIndex = NSNotFound;
characterIndex = [layoutManager characterIndexForPoint:location inTextContainer:self.textContainer fractionOfDistanceBetweenInsertionPoints:NULL];
return characterIndex;
}
Also check out WWDC 2013 intro to TextKit Demo
ios7 text utilities
I want to implement custom text fields. Here is a screenshot of what I want to implement:
So I have a UITableView with cells, and in each cell I have several text fields: one for date, one for name, one for theme, and one for text. But the UI requires theme and text to be next to each other (as can you see in the picture). I wanted to implement this as a single UITextField, but as far as I know, UITextField supports only one type of font. So maybe someone will give me a piece of advice how to implement the design shown in the screenshot — do I have to draw custom text fields, or are there simpler solutions? Any code samples or propositions would be helpful.
For iOS 6:
You can set a UITextField's attributed string:
#property(nonatomic,copy) NSAttributedString *attributedText;
I suggest to use a NSMutableAttributedString and to exploit polymorphism.
A mutable attributed string can hold vary attributes for each piece of the string. So you can add an attribute only for a certain range of the string with this method:
- (void)setAttributes:(NSDictionary *)attributes range:(NSRange)aRange;
For iOS 5:
Use directly this UITextField's property:
- (void)setAttributes:(NSDictionary *)attributes range:(NSRange)aRange;