Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Add a click event to some text in ios NSString

I have the following code and want to make parts of my text be clickable and call another UIViewController (not a website).

NSMutableAttributedString *str = [[NSMutableAttributedString alloc] initWithString:@"testing it out @clickhere"];
NSInteger length = str.length;
[str addAttribute:NSForegroundColorAttributeName value:[UIColor bestTextColor] range:NSMakeRange(0,length)];

The NSMutableAttributedString gets set to a UILabel like so:

label.attributedText = str;

Whats the best way to do this? I can't seem to find a great answer.

An example of what I want is suppose I have a UILabel like so with the following text:

This is my label.  Click here to go to UIViewController1 and then go to UIViewController1 by this #tag.

I want the text "here" to be passed for the first click event and the word "#tag" to be passed to the same click event.

like image 638
cdub Avatar asked Jan 19 '15 22:01

cdub


3 Answers

What if you used the value field to pass in the destination?

[attributedString addAttribute:NSLinkAttributeName
                         value:[@"destinationController1" stringByAppendingString:username]
                         range:range];

Then override the delegate method:

- (BOOL)textView:(UITextView *)textView shouldInteractWithURL:(NSURL *)URL inRange:(NSRange)characterRange
{
    if ([URL.scheme isEqualToString:@"destinationController1"]) {
        // Launch View controller
        return NO;
    }
    return YES;
}
like image 153
scott Avatar answered Oct 18 '22 23:10

scott


My solution requires the use of a UITextView (which is significantly easier, and I urge that you use it instead).

Swift

class ViewController: UIViewController {
    @IBOutlet weak var textView:UITextView!;

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        let gestureRecognizer = UITapGestureRecognizer(target: self, action: "textViewTapped:");
        gestureRecognizer.numberOfTapsRequired = 1;
        gestureRecognizer.numberOfTouchesRequired = 1;
        self.textView.addGestureRecognizer(gestureRecognizer);
    }

    func textViewTapped(sender: UITapGestureRecognizer) {
        let wordTarget = "here";

        let word = UITextView.getWordAtPosition(sender.locationInView(self.textView), textView: self.textView);
        if word == wordTarget {
            let plainString = self.textView.attributedText.string;
            let substrings = NSMutableArray();
            let scanner = NSScanner(string: plainString);
            scanner.scanUpToString("#", intoString: nil);
            while !scanner.atEnd {
                var substring:NSString? = nil;
                scanner.scanString("#", intoString: nil);
                let space = " ";
                if scanner.scanUpToString(space, intoString: &substring) {
                    // If the space immediately followed the #, this will be skipped
                    substrings.addObject(substring!);
                }
                scanner.scanUpToString("#", intoString: nil);
                //Scan all characters before next #
            }
            println(substrings.description);
            //Now you got your substrings in an array, so use those for your data passing (in a segue maybe?)
            ...

        }
    }

}

extension UITextView {
    class func getWordAtPosition(position: CGPoint!, textView: UITextView!) -> String? {
        //Remove scrolloffset
        let correctedPoint = CGPointMake(position.x, textView.contentOffset.y + position.y);
        //Get location in text from uitextposition at a certian point
        let tapPosition = textView.closestPositionToPoint(correctedPoint);
        //Get word at the position, will return nil if its empty.
        let wordRange = textView.tokenizer.rangeEnclosingPosition(tapPosition, withGranularity: UITextGranularity.Word, inDirection: UITextLayoutDirection.Right.rawValue);
        return textView.textInRange(wordRange!);
    }
}

Objective-C

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    UITapGestureRecognizer *gestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(textViewTapped:)];
    gestureRecognizer.numberOfTouchesRequired = 1;
    gestureRecognizer.numberOfTapsRequired = 1;
    [self.textView addGestureRecognizer:gestureRecognizer];
}

- (void)textViewTapped:(UITapGestureRecognizer *)sender {
    NSString *wordTarget = @"here";

    NSString* word = [self getWordAtPosition:[sender locationInView:self.textView] textView:self.textView];
    if ([word isEqualToString:wordTarget]) {
        NSString *plainString = self.textView.attributedText.string;
        NSMutableArray* substrings = [[NSMutableArray alloc]init];
        NSScanner *scanner = [[NSScanner alloc]initWithString:plainString];
        [scanner scanUpToString:@"#" intoString:nil];
        while (![scanner isAtEnd]) {
            NSString* substring = nil;
            [scanner scanString:@"#" intoString:nil];
            NSString* space = @" ";
            if ([scanner scanUpToString:space intoString:&substring]) {
                [substrings addObject:substring];
            }
            [scanner scanUpToString:@"#" intoString:nil];
        }

        //Now you got your substrings in an array, so use those for your data passing (in a segue maybe?)
        ...

    }
}

- (NSString*)getWordAtPosition:(CGPoint)position textView:(UITextView *)textView {
    //remove scrollOffset
    CGPoint correctedPoint = CGPointMake(position.x, textView.contentOffset.y + position.y);
    UITextPosition *tapPosition = [textView closestPositionToPoint:correctedPoint];
    UITextRange *wordRange = [textView.tokenizer rangeEnclosingPosition:tapPosition withGranularity:UITextGranularityWord inDirection:UITextLayoutDirectionRight];
    return [textView textInRange:wordRange];
}

Basically you need to add a gesture recognizer to get the tap point in your textview. Then, you get the word using the category method provided in the extension area. After, you check what the word is (where we want the word "here"). Then, we collect the hashtags you have provided.

All you have to do is add a performSegueWithIdentifier method, and pass it accordingly.

like image 3
Nate Lee Avatar answered Oct 18 '22 23:10

Nate Lee


In addition to @Nate Lee answer, updated the extension for Swift 4.0 version:

extension UITextView {
    class func getWordAtPosition(position: CGPoint!, textView: UITextView!) -> String? {
    //Remove scrolloffset
    let correctedPoint = CGPoint(x: position.x, y: (textView.contentOffset.y + position.y))
    //Get location in text from uitextposition at a certian point
    let tapPosition = textView.closestPosition(to: correctedPoint)
    //Get word at the position, will return nil if its empty.
    let wordRange = textView.tokenizer.rangeEnclosingPosition(tapPosition!, with: .word, inDirection: UITextLayoutDirection.right.rawValue)
    return textView.text(in: wordRange!)
    }
}
like image 1
Soumen Avatar answered Oct 18 '22 23:10

Soumen