Custom TextView Control With Hovering (Objective-C Version)
Learn how to make a custom textview for mobile where tapping and holding over a long piece of text displays the whole message without truncation.
Join the DZone community and get the full member experience.
Join For FreeI'm going to explain about a custom textview control with hovering.
The scenario which directed me to build up a control like this as follows. Let's say you have a textview (max number of lines: 1) with a limited width. When you set a lengthy text on it, it will automatically truncate some part of the text and add 3 dots (...) towards the end.
The solution which I proposed would work like this. Say you have a textview with a lengthy text. When you tap and hold it, it'll bring up the entire text (without any truncation) in a hover view/separate view.
Example: A textview with a lengthy text.
When you tap and hold the textview...
Now I'm going to the details of the actual implementation.
.h file
#import <UIKit/UIKit.h>
@interface CustomTextView : UITextView
@property(atomic, weak) UIViewController* currentViewController;
@property(atomic, strong) UIView* hoverView;
@end
.m file
#import <Foundation/Foundation.h>
#import "CustomTextView.h"
#import "UIHelper.h"
@implementation CustomTextView
- (id)initWithCoder:(NSCoder *)aDecoder{
self = [super initWithCoder:aDecoder];
if (self)
[self initialSetup];
return self;
}
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self)
[self initialSetup];
return self;
}
- (void)onLongPress:(UILongPressGestureRecognizer *) longPressGestureRecognizer{
if (longPressGestureRecognizer.state == UIGestureRecognizerStateBegan){
if(longPressGestureRecognizer.view != nil){
if ([longPressGestureRecognizer.view isKindOfClass:[UITextView class]]) {
UITextView *textView = (UITextView*)longPressGestureRecognizer.view;
self.currentViewController = self.window.rootViewController;
NSString *text = textView.text;
CGSize sizeRequired = [textView.text sizeWithAttributes:@{NSFontAttributeName :textView.font}];
CGFloat widthRequired = sizeRequired.width;
CGFloat widthAvailable = textView.bounds.size.width;
if(widthRequired <= widthAvailable) return;
[textView sizeToFit];
CGFloat navigationBarHeight = 0;
if(self.currentViewController.navigationController.navigationBar != nil) navigationBarHeight = self.currentViewController.navigationController.navigationBar.frame.size.height;
CGFloat toolbarHeight = 0;
UIToolbar *toolbar = [UIHelper findToolbar:self.currentViewController.view];
if (toolbar != nil) toolbarHeight = toolbar.frame.size.height;
CGFloat screenHeight = [UIScreen mainScreen].bounds.size.height;
CGFloat availableHeight = screenHeight - (navigationBarHeight + toolbarHeight);
CGPoint currentRelativePoint = [self.currentViewController.view convertPoint:textView.frame.origin toView:nil];
CGRect originalRect = textView.frame;
CGFloat currentYPos = (currentRelativePoint.y + originalRect.size.height);
UITextView *innerTextView = [[UITextView alloc] initWithFrame:CGRectMake(0, 0, originalRect.size.width, originalRect.size.height)];
[innerTextView setFont:[UIFont systemFontOfSize:12]];
[innerTextView setText:text];
[innerTextView setFrame:CGRectMake(0, 0, originalRect.size.width, innerTextView.contentSize.height)];
CGRect innerRect = innerTextView.frame;
if ((availableHeight - currentYPos) >= innerRect.size.height)
self.hoverView = [[UIView alloc] initWithFrame:CGRectMake(originalRect.origin.x, (originalRect.origin.y + originalRect.size.height), innerRect.size.width, innerRect.size.height)];
else
self.hoverView = [[UIView alloc] initWithFrame:CGRectMake(originalRect.origin.x, (originalRect.origin.y - innerRect.size.height), innerRect.size.width, innerRect.size.height)];
[self.hoverView.layer setBorderColor:[UIColor purpleColor].CGColor];
[self.hoverView.layer setBorderWidth:1];
[self.hoverView.layer setCornerRadius:4];
[self.hoverView addSubview:innerTextView];
[textView.superview addSubview:self.hoverView];
}
}
}
else{
if (longPressGestureRecognizer.state == UIGestureRecognizerStateCancelled
|| longPressGestureRecognizer.state == UIGestureRecognizerStateFailed
|| longPressGestureRecognizer.state == UIGestureRecognizerStateEnded){
if (self.hoverView != nil){
UITextView *innerTextView = [self.hoverView subviews][0];
NSMutableArray *words = [NSMutableArray arrayWithArray:[innerTextView.text componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]];
NSInteger wordCount = [words count];
double noOfWordsPerSec = 3.33;//average readers are the majority and only reach around 200 wpm.
double noOfSecsRequired = (ceil)(wordCount/noOfWordsPerSec);
double bonusSecs = 1.0;
[UIView animateWithDuration:1 delay:(noOfSecsRequired + bonusSecs) options:0
animations:^{
[self.hoverView setAlpha:0.0f];
}
completion:^(BOOL finished){
if (finished)
[self.hoverView removeFromSuperview];
}];
}
}
}
}
- (void)initialSetup{
self.textContainer.maximumNumberOfLines = 1;
self.textContainer.lineBreakMode = NSLineBreakByTruncatingTail;
self.scrollEnabled = false;
self.editable = FALSE;
self.selectable = FALSE;
[self setDataDetectorTypes:UIDataDetectorTypeAll];
for (UIGestureRecognizer *recognizer in self.gestureRecognizers){
if ([recognizer isKindOfClass:[UILongPressGestureRecognizer class]]){
recognizer.enabled = NO;
}
}
UILongPressGestureRecognizer *longPressGesRec = [[UILongPressGestureRecognizer alloc]
initWithTarget:self
action:@selector(onLongPress:)];
[self addGestureRecognizer:longPressGesRec];
}
- (void)awakeFromNib{
[super awakeFromNib];
}
@end
Usage:
CustomTextView *ctv = [[CustomTextView alloc] initWithFrame:CGRectMake(65, 300, 150, 30)];
ctv.text = @"replace this with a lengthy text.....";
[self.view addSubview:ctv];
Please note the following:
- The width of the hover view is matched with the width of the textview.
- Depending on the available height, the hover view will appear above or below the textview.
- The hover view will automatically fade away after a certain amount of time - the displaying time will be calculated based on the number of words in the given text.
Thank you and happy coding... :)
Opinions expressed by DZone contributors are their own.
Comments