Luke35
Luke35

Reputation: 61

How do I create a Textfield for Tag-Like-Text Input in iOS?

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

Answers (2)

aiman86
aiman86

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):

  1. 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];
             }
         }];
    }
    
    1. Now you can simply use the attribute "Tag" in an NSAttributedString (with any value) and the text view will render the tagged text with similar look and feel to Facebook-like tags.

My implementation at github also provides NSTextField and NSTextFieldCell implementations if you're interested in using it within table views:

enter image description here

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

markich
markich

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

Related Questions