Reputation: 61
I'm creating a app where the User can input text (words or short phrases) by pressing several buttons.
The text gets inserted into a UITextField, where the user can input additional text of his own. To simplify interaction with the already inserted text, I'd love to have a behavior that is similar to the way the standard messages-app acts when searching contacts for a new conversation, or how Evernote handles Tags.
That means, after they are inserted, the words or phrases are only selectable as a whole, and pressing the Delete-Button on the keyboard selects the whole "Tag" first and deletes it on the next press.
Is there a way to make the standard UITextField behave like this, or is there an open-source-implementation of a UITextField that does?
Upvotes: 0
Views: 1563
Reputation: 71
I needed something more hybrid on OS X that allows a mix of Facebook-like tags as well as normal text. So I've built my own implementation https://github.com/aiman86/ANTaggedTextView, this takes care of the view rendering only, and not auto completion or selections (planning to the implement latter soon):
Subclass NSTextView and override drawRect
- (void)drawRect:(NSRect)dirtyRect {
[super drawRect:dirtyRect];
[self.attributedString enumerateAttributesInRange:(NSRange){0, self.string.length} options:NSAttributedStringEnumerationReverse usingBlock:
^(NSDictionary *attributes, NSRange range, BOOL *stop) {
if ([attributes objectForKey:@"Tag"] != nil)
{
NSDictionary* tagAttributes = [self.attributedString attributesAtIndex:range.location effectiveRange:nil];
NSSize oneCharSize = [@"a" sizeWithAttributes:tagAttributes];
NSRange activeRange = [self.layoutManager glyphRangeForCharacterRange:range actualCharacterRange:NULL];
NSRect tagRect = [self.layoutManager boundingRectForGlyphRange:activeRange inTextContainer:self.textContainer];
tagRect.origin.x += self.textContainerOrigin.x;
tagRect.origin.y += self.textContainerOrigin.y;
tagRect = [self convertRectToLayer:tagRect];
NSRect tagBorderRect = (NSRect){ (NSPoint){tagRect.origin.x-oneCharSize.width*0.25, tagRect.origin.y+1}, (NSSize){tagRect.size.width+oneCharSize.width*0.5, tagRect.size.height} };
[NSGraphicsContext saveGraphicsState];
NSBezierPath* path = [NSBezierPath bezierPathWithRoundedRect:tagBorderRect xRadius:3.0f yRadius:3.0f];
NSColor* fillColor = [NSColor colorWithCalibratedRed:237.0/255.0 green:243.0/255.0 blue:252.0/255.0 alpha:1];
NSColor* strokeColor = [NSColor colorWithCalibratedRed:163.0/255.0 green:188.0/255.0 blue:234.0/255.0 alpha:1];
NSColor* textColor = [NSColor colorWithCalibratedRed:37.0/255.0 green:62.0/255.0 blue:112.0/255.0 alpha:1];
[path addClip];
[fillColor setFill];
[strokeColor setStroke];
NSRectFillUsingOperation(tagBorderRect, NSCompositeSourceOver);
NSAffineTransform *transform = [NSAffineTransform transform];
[transform translateXBy: 0.5 yBy: 0.5];
[path transformUsingAffineTransform: transform];
[path stroke];
[transform translateXBy: -1.5 yBy: -1.5];
[path transformUsingAffineTransform: transform];
[path stroke];
NSMutableDictionary* attrs = [NSMutableDictionary dictionaryWithDictionary:tagAttributes];
NSFont* font = [tagAttributes valueForKey:NSFontAttributeName];
font = [[NSFontManager sharedFontManager] convertFont:font toSize:[font pointSize] - 0.25];
[attrs addEntriesFromDictionary:@{NSFontAttributeName: font, NSForegroundColorAttributeName: textColor}];
[[self.attributedString.string substringWithRange:range] drawInRect:tagRect withAttributes:attrs];
[NSGraphicsContext restoreGraphicsState];
}
}];
}
My implementation at github also provides NSTextField and NSTextFieldCell implementations if you're interested in using it within table views:
Hope that helps someone who is in a similar situation, please feel free to comment if you see shortcomings, I'm quite new to Cocoa app development.
Upvotes: 0
Reputation: 356
I needed something similar on my OS X application. There they are using something called NSTokenField
but unfortunately there is no equivalent on iOS platform. So you are basically stuck with creating your own tokens or use existing third-party libraries. If you need details on how to create custom tokens, let me know.
P.S. creating custom tokens requires overriding drawRect
method and some usage of Bezier paths.
Expanded answer:
I'll show you how to create tokens on NSTextView
(which contains text, but can also contains NSTextAttachment
. Also, code works on Cocoa (but with little tweaking the same can be achieved in Cocoa Touch.
1.) Create class which extends NSTextAttachmentCell
2.) Override method -(void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView
- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView
{
[self drawWithFrame:cellFrame inView:controlView characterIndex:NSNotFound layoutManager:nil];
}
And implementation of - (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView characterIndex:(NSUInteger)charIndex layoutManager:(NSLayoutManager *)layoutManager
is like this:
- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView characterIndex:(NSUInteger)charIndex layoutManager:(NSLayoutManager *)layoutManager
{
NSColor* bgColor = [NSColor redColor];
NSColor* borderColor = [NSColor blackColor];
NSRect frame = cellFrame;
CGFloat radius = ceilf([self cellSize].height / 2.f);
NSBezierPath* roundedRectanglePath = [NSBezierPath bezierPathWithRoundedRect: NSMakeRect(NSMinX(frame) + 0.5, NSMinY(frame) + 3.5, NSWidth(frame) - 1, NSHeight(frame) - 1) xRadius: radius yRadius: radius];
[bgColor setFill];
[roundedRectanglePath fill];
[borderColor setStroke];
[roundedRectanglePath setLineWidth: 1];
[roundedRectanglePath stroke];
CGSize size = [[self stringValue] sizeWithAttributes:@{NSFontAttributeName:defaultFont}];
CGRect textFrame = CGRectMake(cellFrame.origin.x + (cellFrame.size.width - size.width)/2,
cellFrame.origin.y + 2.f,
size.width,
size.height);
[[self stringValue] drawInRect:textFrame withAttributes:@{NSFontAttributeName:defaultFont, NSForegroundColorAttributeName: [NSColor whiteColor]}];
}
All the code should be self explanatory, but if you have any questions, please ask. Hope it helps.
Upvotes: 2