70

I'm trying to filter an array according to one of it's string fields.

Both nameLower and filterLower has NSString value inside, and yet i keep getting:

__NSCFString containsString:]: unrecognized selector sent to instance 0x7f876b79e160

-(void) filterFriendsArray:(NSString*)filter {
    [_filteredFriendsArray removeAllObjects];
    for (FacebookUser* user in _friendsArray)
    {
        NSString* nameLower = [user.user.name lowercaseString];
        NSString* filterLower = [filter lowercaseString];
        if ([nameLower containsString:filterLower])
            [_filteredFriendsArray addObject:user];
    }
    _displayedFriendsArray = _filteredFriendsArray;
}
Asaf Nevo
  • 11,338
  • 23
  • 79
  • 154

6 Answers6

119

If you want your code to work on iOS 7 as well as iOS 8 you should use one of the rangeOfString calls instead. Basically if the range returned has a length of zero, the substring is not there.

/* These methods return length==0 if the target string is not found. So, to check for containment: ([str rangeOfString:@"target"].length > 0).  Note that the length of the range returned by these methods might be different than the length of the target string, due composed characters and such.
*/
- (NSRange)rangeOfString:(NSString *)aString;
- (NSRange)rangeOfString:(NSString *)aString options:(NSStringCompareOptions)mask;
- (NSRange)rangeOfString:(NSString *)aString options:(NSStringCompareOptions)mask range:(NSRange)searchRange;
- (NSRange)rangeOfString:(NSString *)aString options:(NSStringCompareOptions)mask range:(NSRange)searchRange locale:(NSLocale *)locale NS_AVAILABLE(10_5, 2_0);

Obviously it's trivial to implement containsString yourself in a category using rangeOfString:

@implementation NSString (Contains)

- (BOOL)myContainsString:(NSString*)other {
  NSRange range = [self rangeOfString:other];
  return range.length != 0;
}

@end
w0mbat
  • 2,430
  • 1
  • 15
  • 16
  • on osx rangeOfString seems to return nil and not 0 if the string isn't contained – Morten J Apr 30 '15 at 06:35
  • 2
    // "on osx rangeOfString seems to return nil and not 0 if the string isn't contained" The last time I checked, nil was equal to zero. – w0mbat May 02 '15 at 22:55
  • 5
    wouldbe so nice to see this issue at compile time – user230910 Jun 21 '15 at 08:50
  • 2
    As per Apple's documentation, you should compare the result with `NSNotFound` instead of `0` or `nil`. – Christoffer Årstrand Aug 25 '15 at 07:22
  • 4
    Yes, check the range's location for NSNotFound - `return range.location != NSNotFound;` – johnpatrickmorgan Sep 03 '15 at 18:52
  • The docs say "Returns {NSNotFound, 0} if aString is not found or is empty". That's a location of NSNotFound and a length of zero. Note that these things always occur together in the struct, because you can only get a found length of zero if nothing was found, so checking for either is fine. Checking for a found length of zero, is just as good as checking for a location of NSNotFound, and is very slightly faster as checking for nonzero is cheaper than comparing to a constant. – w0mbat Sep 14 '15 at 17:29
  • I had this same problem on OSX 10.8 even though I set my XCode to compile for 10.8 (and warn me if errors for 10.8). For some reason, XCode 7.1 has a bug and doesn't flag this issue. So, I used your technique to workaround the problem. – Volomike Mar 30 '16 at 04:27
8

compare rangeOfString with NSNotFound

NSRange range = [self rangeOfString:other];
if(range.location != NSNotFound){
    //do something
}
Deepak Badiger
  • 448
  • 7
  • 18
3

Use following:

if (![[NSString class] respondsToSelector:@selector(containsString)])
     {
         //ios 7
         NSRange range = [mystring rangeOfString:other];
         if(range.location != NSNotFound){
           //do something
         }
     }
     else  //for iOS 8 
     {
          if ([mystring containsString: other])
          {
              //do something
          }                             
     }
Ram G.
  • 3,045
  • 2
  • 25
  • 31
0

For those who encountered this in XLForm, make sure when you install XLForm using pods

platform :ios, '7'
pod 'XLForm'

It is already fixed in 3.1

from

if ([cellClassString contains:@"/"]) {

}

to

if ([cellClassString rangeOfString:@"/"].location != NSNotFound) {

}
Ted
  • 22,696
  • 11
  • 95
  • 109
0

I encapsulate my solution in YJKit, and you can call -[NSString containsString:] even for old version which below iOS 8.

bool _yj_streq(const char *str1, const char *str2, size_t length) {
    for (int i = 0; i < length; i++) {
        if (*str1++ != *str2++) {
            return false;
        }
    }
    return true;
}

- (BOOL)yj_containsString:(NSString *)string {

    NSAssert(string != nil, @"*** -[%@ containsString:] can not use nil argument.", [self class]);

    size_t len1 = (size_t)self.length;
    size_t len2 = (size_t)string.length;

    if (len1 == 0 || len2 == 0 || len1 < len2) {
        return NO;
    }

    const char *str1 = self.UTF8String;
    const char *str2 = string.UTF8String;

    for (size_t i = 0; i <= len1 - len2; i++) {
        const char *substr1 = str1 + i;
        if (_yj_streq(substr1, str2, len2)) {
            return YES;
        } else {
            continue;
        }
    }

    return NO;
}

Here is my source code: https://github.com/huang-kun/YJKit/blob/master/YJKit/Base/Foundation/Categories/Generics/NSString%2BYJCompatible.m

huang-kun
  • 1
  • 2
  • 1
    While this link may answer the question, it is better to include the essential parts of the answer here and provide the link for reference. Link-only answers can become invalid if the linked page changes. – Roman Marusyk Aug 05 '16 at 10:31
-1

Swift version of the answer given by w0mbat:

extension NSString {
    func compatibleContainsString(string: NSString) -> Bool{
        let range = self.rangeOfString(string as String)
        return range.length != 0
    }
}
Chris C
  • 3,221
  • 1
  • 27
  • 31