3
let a = """
a
️b
"""
a.contains("\n") // false

It seems there is a strange character in the string right after the return.

a.forEach(){ c in
    print(Character(String(c)).asciiValue)
}

Output: 
Optional(97)
Optional(10) // the return 
nil
Optional(98)

Is this a bug of contains method that results in negatively recognizing the return character while it is followed by some strange ones?

JsW
  • 1,682
  • 3
  • 22
  • 34

1 Answers1

1

First, I printed out the unicode scalars in your string:

print(a.unicodeScalars.map { $0.value })
// [97, 10, 65039, 98]

And found out that your string indeed contains the line feed character \n, which is the value 10. However, it is followed by U+FE0F (65039), one of those variation selectors in unicode.

The overload of contains you are calling here is contains(StringProtocol), not contains(Character). The former will perform a "smarter" kind of comparison, or as the Xcode's Quick Help documentation calls it, "non-literal":

Summary

Returns true iff other is non-empty and contained within self by case-sensitive, non-literal search.

Discussion

Equivalent to self.rangeOfString(other) != nil

I can't seem to find this documentation online though... All I could find was this discussion showing contains is smart enough to recognise that "ß" means "ss".

Anyway, the point is, contains does not do a character-by-character search. It does whatever it think "makes sense".

Here are a few ways to make it print true:

  • If you add the variation selector in to the argument to contains, it prints true:

      print(a.contains("\n️")) // you can't see it, but there *is* a variation selector after the n
    
  • Check whether the unicodeScalars contain the \n character:

      print(a.unicodeScalars.contains("\n"))
    
  • Use the contains(Character) overload:

      print(a.contains("\n" as Character))
    
  • Use range(of:) with .literal option:

      print(a.rangeOfString("\n", options: [.literal]) != nil)
    
Community
  • 1
  • 1
Sweeper
  • 213,210
  • 22
  • 193
  • 313