Arthur Garza
Arthur Garza

Reputation: 2071

Swift startsWith method?

Is there such thing as a startsWith() method or something similar in Swift?

I'm basically trying to check if a certain string starts with another string. I also want it to be case insensitive.

As you might be able to tell, I'm just trying to do a simple search feature but I seem to be failing miserably at this.

This is what I'd like:

typing in "sa" should give me results for "San Antonio", "Santa Fe", etc. typing in "SA" or "Sa" or even "sA" should also return "San Antonio" or "Santa Fe".

I was using

self.rangeOfString(find, options: NSStringCompareOptions.CaseInsensitiveSearch) != nil 

prior to iOS9 and it was working just fine. After upgrading to iOS9, however, it stopped working and now searches are case sensitive.

    var city = "San Antonio"
    var searchString = "san "
    if(city.rangeOfString(searchString, options: NSStringCompareOptions.CaseInsensitiveSearch) != nil){
        print("San Antonio starts with san ");
    }

    var myString = "Just a string with san within it"

    if(myString.rangeOfString(searchString, options: NSStringCompareOptions.CaseInsensitiveSearch) != nil){
        print("I don't want this string to print bc myString does not start with san ");
    }

Upvotes: 179

Views: 62020

Answers (8)

Alessandro Vendruscolo
Alessandro Vendruscolo

Reputation: 14877

You can get away with many of these answers, however if you're matching something that's out of your control (for example names coming from different languages) you have to take an extra step to be more user friendly.

You should use String.folding(options:locale:) on both the search term and the values to search on.

Specifically, you should pass both diacriticInsensitive and caseInsensitive options. You can also specify a Locale to use (or let it use the system locale).

With this, you'll drop diacritics and make the string lowercase in a single pass.

let haystack = [
    "Beyoncé Knowles",
    "Chloë Grace Moretz",
    "Chloë Sevigny",
    "Renée Fleming",
    "Renée Zellweger",
]

let search = "réNèe" // → renee

let searchOptions: [String.CompareOptions] = [.caseInsensitive, .diacriticInsensitive]
let needle = search.folding(options: searchOptions)

haystack.filter {
    $0.folding(options: searchOptions).starts(with: search)
}

// → "Renée Fleming", "Renée Zellweger",

Upvotes: 1

Gary Makin
Gary Makin

Reputation: 3169

Edit: updated for Swift 3.

The Swift String class does have the case-sensitive method hasPrefix(), but if you want a case-insensitive search you can use the NSString method range(of:options:).

Note: By default, the NSString methods are not available, but if you import Foundation they are.

So:

import Foundation
var city = "San Antonio"
var searchString = "san "
let range = city.range(of: searchString, options:.caseInsensitive)
if let range = range {
    print("San Antonio starts with san at \(range.startIndex)");
}

The options can be given as either .caseInsensitive or [.caseInsensitive]. You would use the second if you wanted to use additional options, such as:

let range = city.range(of: searchString, options:[.caseInsensitive, .backwards])

This approach also has the advantage of being able to use other options with the search, such as .diacriticInsensitive searches. The same result cannot be achieved simply by using . lowercased() on the strings.

Upvotes: 11

CaOs433
CaOs433

Reputation: 1215

In Swift 4 with extensions

My extension-example contains 3 functions: check do a String start with a subString, do a String end to a subString and do a String contains a subString.

Set the isCaseSensitive-parameter to false, if you want to ignore is the characters "A" or "a", otherwise set it to true.

See the comments in the code for more information of how it works.

Code:

    import Foundation

    extension String {
        // Returns true if the String starts with a substring matching to the prefix-parameter.
        // If isCaseSensitive-parameter is true, the function returns false,
        // if you search "sA" from "San Antonio", but if the isCaseSensitive-parameter is false,
        // the function returns true, if you search "sA" from "San Antonio"

        func hasPrefixCheck(prefix: String, isCaseSensitive: Bool) -> Bool {

            if isCaseSensitive == true {
                return self.hasPrefix(prefix)
            } else {
                var thePrefix: String = prefix, theString: String = self

                while thePrefix.count != 0 {
                    if theString.count == 0 { return false }
                    if theString.lowercased().first != thePrefix.lowercased().first { return false }
                    theString = String(theString.dropFirst())
                    thePrefix = String(thePrefix.dropFirst())
                }; return true
            }
        }
        // Returns true if the String ends with a substring matching to the prefix-parameter.
        // If isCaseSensitive-parameter is true, the function returns false,
        // if you search "Nio" from "San Antonio", but if the isCaseSensitive-parameter is false,
        // the function returns true, if you search "Nio" from "San Antonio"
        func hasSuffixCheck(suffix: String, isCaseSensitive: Bool) -> Bool {

            if isCaseSensitive == true {
                return self.hasSuffix(suffix)
            } else {
                var theSuffix: String = suffix, theString: String = self

                while theSuffix.count != 0 {
                    if theString.count == 0 { return false }
                    if theString.lowercased().last != theSuffix.lowercased().last { return false }
                    theString = String(theString.dropLast())
                    theSuffix = String(theSuffix.dropLast())
                }; return true
            }
        }
        // Returns true if the String contains a substring matching to the prefix-parameter.
        // If isCaseSensitive-parameter is true, the function returns false,
        // if you search "aN" from "San Antonio", but if the isCaseSensitive-parameter is false,
        // the function returns true, if you search "aN" from "San Antonio"
        func containsSubString(theSubString: String, isCaseSensitive: Bool) -> Bool {

            if isCaseSensitive == true {
                return self.range(of: theSubString) != nil
            } else {
                return self.range(of: theSubString, options: .caseInsensitive) != nil
            }
        }
    }

Examples how to use:

For checking do the String start with "TEST":

    "testString123".hasPrefixCheck(prefix: "TEST", isCaseSensitive: true) // Returns false
    "testString123".hasPrefixCheck(prefix: "TEST", isCaseSensitive: false) // Returns true

For checking do the String start with "test":

    "testString123".hasPrefixCheck(prefix: "test", isCaseSensitive: true) // Returns true
    "testString123".hasPrefixCheck(prefix: "test", isCaseSensitive: false) // Returns true

For checking do the String end with "G123":

    "testString123".hasSuffixCheck(suffix: "G123", isCaseSensitive: true) // Returns false
    "testString123".hasSuffixCheck(suffix: "G123", isCaseSensitive: false) // Returns true

For checking do the String end with "g123":

    "testString123".hasSuffixCheck(suffix: "g123", isCaseSensitive: true) // Returns true
    "testString123".hasSuffixCheck(suffix: "g123", isCaseSensitive: false) // Returns true

For checking do the String contains "RING12":

    "testString123".containsSubString(theSubString: "RING12", isCaseSensitive: true) // Returns false
    "testString123".containsSubString(theSubString: "RING12", isCaseSensitive: false) // Returns true

For checking do the String contains "ring12":

    "testString123".containsSubString(theSubString: "ring12", isCaseSensitive: true) // Returns true
    "testString123".containsSubString(theSubString: "ring12", isCaseSensitive: false) // Returns true

Upvotes: 2

Cœur
Cœur

Reputation: 38667

To answer specifically case insensitive prefix matching:

in pure Swift (recommended most of the time)

extension String {
    func caseInsensitiveHasPrefix(_ prefix: String) -> Bool {
        return lowercased().hasPrefix(prefix.lowercased())
    }
}

or:

extension String {
    func caseInsensitiveHasPrefix(_ prefix: String) -> Bool {
        return lowercased().starts(with: prefix.lowercased())
    }
}

note: for an empty prefix "" both implementations will return true

using Foundation range(of:options:)

extension String {
    func caseInsensitiveHasPrefix(_ prefix: String) -> Bool {
        return range(of: prefix, options: [.anchored, .caseInsensitive]) != nil
    }
}

note: for an empty prefix "" it will return false

and being ugly with a regex (I've seen it...)

extension String {
    func caseInsensitiveHasPrefix(_ prefix: String) -> Bool {
        guard let expression = try? NSRegularExpression(pattern: "\(prefix)", options: [.caseInsensitive, .ignoreMetacharacters]) else {
            return false
        }
        return expression.firstMatch(in: self, options: .anchored, range: NSRange(location: 0, length: characters.count)) != nil
    }
}

note: for an empty prefix "" it will return false

Upvotes: 16

Bueno
Bueno

Reputation: 1950

In swift 4 func starts<PossiblePrefix>(with possiblePrefix: PossiblePrefix) -> Bool where PossiblePrefix : Sequence, String.Element == PossiblePrefix.Element will be introduced.

Example Use:

let a = 1...3
let b = 1...10

print(b.starts(with: a))
// Prints "true"

Upvotes: 7

jobima
jobima

Reputation: 5930

use hasPrefix instead of startsWith.

Example:

"hello dolly".hasPrefix("hello")  // This will return true
"hello dolly".hasPrefix("abc")    // This will return false

Upvotes: 427

user2990759
user2990759

Reputation: 127

Swift 3 version:

func startsWith(string: String) -> Bool {
    guard let range = range(of: string, options:[.caseInsensitive]) else {
        return false
    }
    return range.lowerBound == startIndex
}

Upvotes: 1

Oliver Atkinson
Oliver Atkinson

Reputation: 8029

here is a Swift extension implementation of startsWith:

extension String {

  func startsWith(string: String) -> Bool {

    guard let range = rangeOfString(string, options:[.AnchoredSearch, .CaseInsensitiveSearch]) else {
      return false
    }

    return range.startIndex == startIndex
  }

}

Example usage:

var str = "Hello, playground"

let matches    = str.startsWith("hello") //true
let no_matches = str.startsWith("playground") //false

Upvotes: 14

Related Questions