TharakaNirmana
TharakaNirmana

Reputation: 10353

NSMutableAttributedString on UITextView not clickable

I have a UITextView and String is set with an attributed text that says "See More". I have also added the gesture for the UITextView. Gesture works when the click is performed on any other place but not when tapped on "See More".

I need the click to respond on "See More" click.

Below is how I have implemented:

//link text
 NSDictionary *linkAttributes = @{ NSForegroundColorAttributeName : [UIColor colorWithRed:0.05 green:0.4 blue:0.65 alpha:1.0],
                                                  NSFontAttributeName : [UIFont fontWithName:@"Roboto-Regular" size:10.0],
                                                  NSUnderlineStyleAttributeName : @(NSUnderlineStyleSingle) };

[attributedString setAttributes:linkAttributes range:linkRange];

self.classDesc.userInteractionEnabled = YES;
[self.classDesc addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleExpand:)]];
self.classDesc.selectable = YES;
 // Assign attributedText to UILabel
 self.classDesc.attributedText = attributedString;

Below is the gesture listener:

- (void)handleExpand:(UITapGestureRecognizer *)tapGesture
{
    self.classDesc.text = selectedClasses.classDescription;
    [self textViewDidChange:self.classDesc];

       CGSize sizeThatFitsTextView = [self.classDesc sizeThatFits:CGSizeMake(self.classDesc.frame.size.width, MAXFLOAT)];
    self.descHeight.constant = sizeThatFitsTextView.height ;


}

Any help is greatly appreciated.

Upvotes: 1

Views: 479

Answers (3)

Prasad De Zoysa
Prasad De Zoysa

Reputation: 2567

You can use UILabel for this, Here's how to do,

First create a category class for UITapGestureRecognizer and add below method,

/**
 Returns YES if the tap gesture was within the specified range of the attributed text of the label.
 */
- (BOOL)didTapAttributedTextInLabel:(UILabel *)label inRange:(NSRange)targetRange {
    NSParameterAssert(label != nil);

    CGSize labelSize = label.bounds.size;
    // create instances of NSLayoutManager, NSTextContainer and NSTextStorage
    NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init];
    NSTextContainer *textContainer = [[NSTextContainer alloc] initWithSize:CGSizeZero];
    NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:label.attributedText];

    // configure layoutManager and textStorage
    [layoutManager addTextContainer:textContainer];
    [textStorage addLayoutManager:layoutManager];

    // configure textContainer for the label
    textContainer.lineFragmentPadding = 0.0;
    textContainer.lineBreakMode = label.lineBreakMode;
    textContainer.maximumNumberOfLines = label.numberOfLines;
    textContainer.size = labelSize;

    // find the tapped character location and compare it to the specified range
    CGPoint locationOfTouchInLabel = [self locationInView:label];
    CGRect textBoundingBox = [layoutManager usedRectForTextContainer:textContainer];
    CGPoint textContainerOffset = CGPointMake((labelSize.width - textBoundingBox.size.width) * 0.5 - textBoundingBox.origin.x,
                                              (labelSize.height - textBoundingBox.size.height) * 0.5 - textBoundingBox.origin.y);
    CGPoint locationOfTouchInTextContainer = CGPointMake(locationOfTouchInLabel.x - textContainerOffset.x,
                                                         locationOfTouchInLabel.y - textContainerOffset.y);
    NSInteger indexOfCharacter = [layoutManager characterIndexForPoint:locationOfTouchInTextContainer
                                                       inTextContainer:textContainer
                              fractionOfDistanceBetweenInsertionPoints:nil];
    if (NSLocationInRange(indexOfCharacter, targetRange)) {
        return YES;
    } else {
        return NO;
    }
}

Implementation

//Global variables
NSString *longString = @"very very long string";
NSRange moreRange;
NSRange lessRange;

-(void)setReadLessDescriptionText{
    self.lblDescription.userInteractionEnabled = YES;
    [self.lblDescription addGestureRecognizer:
     [[UITapGestureRecognizer alloc] initWithTarget:self
                                             action:@selector(handleTapMore:)]];

    // create your attributed text and keep an variable of your "link" text range
    NSAttributedString *plainText;
    NSAttributedString *linkText;
    plainText = [[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:@"%@ ...", [longString substringWithRange:NSMakeRange(0, 200)]] attributes:nil];//here 200 is  your character limit, it can be any value depending your requirement
    linkText = [[NSMutableAttributedString alloc] initWithString:@"more"
                                                      attributes:@{
                                                                   NSForegroundColorAttributeName:[UIColor blueColor]
                                                                   }];
    NSMutableAttributedString *attrText = [[NSMutableAttributedString alloc] init];
    [attrText appendAttributedString:plainText];
    [attrText appendAttributedString:linkText];

    // Variable -- keep track of the target range so you can compare in the callback
        moreRange = NSMakeRange(plainText.length, linkText.length);

    self.lblDescription.attributedText = attrText;

}

-(void)setReadMoreDescriptionText{
    self.lblDescription.userInteractionEnabled = YES;
    [self.lblDescription addGestureRecognizer:
     [[UITapGestureRecognizer alloc] initWithTarget:self
                                             action:@selector(handleTapLess:)]];

    // create your attributed text and keep an variable of your "link" text range
    NSAttributedString *plainText;
    NSAttributedString *linkText;
    plainText = [[NSMutableAttributedString alloc] initWithString:longString attributes:nil];
    linkText = [[NSMutableAttributedString alloc] initWithString:@" less"
                                                      attributes:@{
                                                                   NSForegroundColorAttributeName:[UIColor blueColor]
                                                                   }];
    NSMutableAttributedString *attrText = [[NSMutableAttributedString alloc] init];
    [attrText appendAttributedString:plainText];
    [attrText appendAttributedString:linkText];

    // Variable -- keep track of the target range so you can compare in the callback
    lessRange = NSMakeRange(plainText.length, linkText.length);

    self.lblDescription.attributedText = attrText;
}

Gesture Methods

- (void)handleTapMore:(UITapGestureRecognizer *)tapGesture {
    BOOL didTapLink = [tapGesture didTapAttributedTextInLabel:self.lblDescription
                                                      inRange:moreRange];

    if (didTapLink) {
        [self setReadMoreDescriptionText];
    }
}

- (void)handleTapLess:(UITapGestureRecognizer *)tapGesture {
    BOOL didTapLink = [tapGesture didTapAttributedTextInLabel:self.lblDescription
                                                      inRange:lessRange];

    if (didTapLink) {
        [self setReadLessDescriptionText];
    }
}

Upvotes: 1

TharakaNirmana
TharakaNirmana

Reputation: 10353

Below two lines of codes were commented, and then the touch on "See More" started to work:

self.classDesc.textContainer.lineFragmentPadding = 0;
self.classDesc.textContainerInset = UIEdgeInsetsZero;

Upvotes: 0

techloverr
techloverr

Reputation: 2617

try this

Implement UITextFieldDelegate protocol In your view controller add the text

@interface YourViewController () <UITextViewDelegate>
In viewDidLoad set yourself as a delegate:

yourUITextView.delegate = self;

// Implement the delegate method below:

- (BOOL)textViewShouldBeginEditing:(UITextView *)textView
{ 
   return NO; 
}

Upvotes: 0

Related Questions