1

I have a user name system similar to twitters using the @ symbol.

I have it set up to message users by typing @username. This is basically identical to mentions in Twitter.

So far I have been able to detect the text following the @ symbol and to stop detecting when it hits white space. This has been done perfectly with NSScanner.

My problem is if I want to message multiple @usernames, the NSScanner will only detect the first occurrence. I need it to detect the latest occurrence. I have found this as a useful tool:

NSRange lastSymbol = [text rangeOfString:@"@" options:NSBackwardsSearch];

I can't seem to figure out how to properly use this in conjunction with NSScanner. Here are 2 examples of my code attempts.

Example 1:

NSString *user = nil;
NSString *text = textView.text;

NSRange lastSymbol = [text rangeOfString:@"@" options:NSBackwardsSearch];

if (lastSymbol.location != NSNotFound) {

    NSScanner *userScanner = [NSScanner scannerWithString:text];
    [userScanner scanUpToString:@"@" intoString:nil];

    NSCharacterSet *charset = [NSCharacterSet characterSetWithCharactersInString:@"@"];

    if (![userScanner isAtEnd]) {

        [userScanner scanUpToCharactersFromSet:charset intoString:nil];
        [userScanner scanCharactersFromSet:charset intoString:nil]
        [userScanner scanUpToCharactersFromSet:charset intoString:&user];


    }
}


if (user.length > 0) {

    NSRange whiteSpaceRange = [user rangeOfCharacterFromSet:[NSCharacterSet whitespaceCharacterSet]];
    if (whiteSpaceRange.location != NSNotFound) {

        // White space, username has ended

    } else {

        // Still getting username
        NSLog(@"%@", user);

    }

}

As you can see, I am just not quite sure how to make these work together to produce the results I am looking for. Any ideas? Thanks!

**************************UPDATE****************************

To be more specific, when a user types the @ symbol and starts typing a username, I want to display a list of their friends based on what they type. So naturally, I want to pull the string following the @ symbol and as the user keeps typing, keep updating the list of their friends. Once the user types a space, we assume they are done typing the user name (usernames cannot contain spaces) so I then stop displaying the table view.

The above code so far is simply detecting and pulling the string following the @ symbol. Here is the output right now:

Textfield Text: Hello @username

enter image description here

This works perfectly, as you can see I can keep checking this text against an array of friends.

Let's say the user wants to message another user in the same text like so:

Hello @username, this is my friend @goodfriend

This time it still displays the first @username, but ignore the second @goodfriend.

I figure the best way to do this is somehow tell the NSScanner to reference the LAST occurrence of the @ symbol, because what I have now is only referencing the first @ symbol.

This might also have to do with the way I am detecting the white space to know the user is done typing the username, and does not need any more suggestions.

Hope this helps, thanks!

Kyle Begeman
  • 7,169
  • 9
  • 40
  • 58
  • Can you post an example of an input/output that you're expecting? – Alladinian Sep 07 '13 at 21:14
  • @Alladinian I updated the original post with more details on what I am trying to do. Simply put, I need to be able to detect when a user starts typing @ symbol and the username and display suggestions based on what they are typing. It works right now with only one user name, but with two usernames, it ignores the second one, and third, etc – Kyle Begeman Sep 07 '13 at 22:23

2 Answers2

2

For working with the live input like this, I would recommend using a completely different approach. Instead of looking for the last "@", look at just the last word, and see if it starts with "@".

NSString *user = nil;
NSString *text = textView.text;
// Find the last whitespace character
NSCharacterSet *whitespace = [NSCharacterSet whitespaceCharacterSet];
NSRange lastWordRange = [text rangeOfCharacterFromSet:whitespace options:NSBackwardsSearch];
// If found, get the index after it. Otherwise, the entire string is one word.
if(lastWordRange.location != NSNotFound)
    lastWordRange.location += lastWordRange.length;
else lastWordRange.location = 0;
// If the index is not at the end, look for a '@' right after it
if(lastWordRange.location < [text length] &&
   [text characterAtIndex:lastWordRange.location] == '@') {
    // Found a '@', use the rest of the string as the username
    user = [text substringFromIndex:lastWordRange.location+1];
}
if(user) NSLog(@"%@", user); // The user is still typing a name

If you really want to use a scanner, you can use the same substringFromIndex: method to get a substring to scan.

NSRange lastSymbol = [text rangeOfString:@"@" options:NSBackwardsSearch];
if (lastSymbol.location != NSNotFound) {
    NSString *substring = [text substringFromIndex:lastSymbol.location];
    NSScanner *userScanner = [NSScanner scannerWithString:substring];
    ...
ughoavgfhw
  • 39,734
  • 6
  • 101
  • 123
  • I see what you are saying and it makes perfect sense! I just happened to figure this out on my own before I saw your post, so I am curious if there are any advantages or disadvantages to using one method over the other? Let me post my method as an answer here, and tell me what you think? Thanks a lot! – Kyle Begeman Sep 07 '13 at 23:18
  • @Kyle That will work. It performs a lot of extra work by scanning the entire string instead of just the last word, but the difference probably won't be significant for 140 character strings. – ughoavgfhw Sep 08 '13 at 00:04
  • Oh ok, I see what you mean now. Performance is absolutely a good reason for using your suggested method. For that, I will mark it as the correct answer while leaving mine for anyone if they have a preference, Thanks a lot for your help, I appreciate it!! – Kyle Begeman Sep 08 '13 at 00:58
0

So here is the solution I came up with. @ugjoavgfhw has a method that also works with the limited testing I have done.

Here is my solution:

NSString *user = nil;
NSString *text = textView.text;

NSRange lastSymbol = [text rangeOfString:@"@" options:NSBackwardsSearch];
NSScanner *userScanner = [NSScanner scannerWithString:text];
if (lastSymbol.location < 140) {
    [userScanner setScanLocation:lastSymbol.location];
}

[userScanner scanUpToString:@"@" intoString:nil];
NSCharacterSet *charset = [NSCharacterSet characterSetWithCharactersInString:@"@"];

if (![userScanner isAtEnd]) {        

    if (lastSymbol.location != NSNotFound) {

        [userScanner scanUpToCharactersFromSet:charset intoString:nil];
        [userScanner scanCharactersFromSet:charset intoString:nil];
        [userScanner scanUpToCharactersFromSet:charset intoString:&user];

    }

}


if (user.length > 0) {

    NSRange whiteSpaceRange = [user rangeOfCharacterFromSet:[NSCharacterSet whitespaceCharacterSet]];
    if (whiteSpaceRange.location != NSNotFound) {

        // White space, username has ended

    } else {

        // Still getting username
        NSLog(@"%@", user);

    }


}

Just to be clear, this code is called in the delegate method:

- (void)textViewDidChange:(UITextView *)textView

Every time the text updates, we perform a backward search looking for the @ symbol. This is assigned to an NSRange.

Then the NSScanner is created. Afterwards we check the lastSymbol.location to make sure it is somewhere below 140 characters (the format is that same as twitter with limited characters).

If it is, then we assign the scanning location to the NSScanner.

The rest works as expected. Hope this helps someone else!

Kyle Begeman
  • 7,169
  • 9
  • 40
  • 58