'How to detect if a user typed an ellipsis in a UITextField?
How can you check if you string has an ellipsis in swift? I have noticed some odd behavior when using the swift string functions and learned that ellipsis are to blame. When a user enters ... as part of the string in a UITextField and then I try to locate a string after that the character count is always off by 2. This is because the string functions are treating ... as 3 characters when locating the index of the string I am searching for, but the character count functions are treating it as 1 character. The solution is pretty simple... when I have an elliptical in the string then adjust the "found" index of the string by 2. The issue is I don't know how to search "does this string have an ellipsis" because this didn't find it:
if heading.contains("...") {
print ("found the ...!")
}
I suspect there is a special way to search for an ellipsis but haven't been able to find out what it is. This is my "find the last space after substringing out the first 30 characters" function that works for strings that don't have an ellipsis:
func breakOutHeadingFromString(fullString: String, charBreakPoint: Int) -> (heading: String, remainingText: String, breakChar: String) {
var heading = fullString
var remainingText = ""
var breakChar = ""
// If the Full string has less characters than break point then just return full blurb as heading and blank out 2
if fullString.count > charBreakPoint {
// Get N characters out of total char count (hardcoded to 30 can this be dynamic?)
var prefix = String(fullString.prefix(charBreakPoint))
// Find the last space in heading so you can continue message there
let lastSpace = prefix.lastIndex(of: " ") ?? prefix.endIndex
var breakPoint = lastSpace
breakChar = "|"
// If there is a /n clip there
if let newLine = prefix.firstIndex(of: "\n") {
prefix = String(prefix[..<newLine])
breakPoint = newLine
breakChar = "\n"
}
// Use the Break Point to split the message in 2
let breakPointInt: Int = fullString.distance(from: fullString.startIndex, to: breakPoint)
// if the string has a eliptical ... then the break point is off by 2 because it 1 char in but 3 in
heading = String(fullString.prefix(breakPointInt))
remainingText = String(fullString.suffix(fullString.count - breakPointInt - 1))
}
return (heading,remainingText,breakChar)
}
Solution 1:[1]
The ellipsis is 1 unicode character, not 3 so it is counted as 1 character and below is what I think is happening in your situation.
This did not find it
if heading.contains("...") {
print ("found the ...!")
}
Because these are 3 periods (3 characters) and different from the ellipsis character (1 character)
Try highlighting with a mouse what you are comparing (...) with the actual ellipsis character (…)
In the first instance, you can highlight each of the dots individually using your mouse and in the second scenario you will not be able to select each individual dot.
Here is some test:
var ellipsisString = "This is … a"
var threeDotString = "This is ... a"
print("Ellipsis character count: \(ellipsisString.count)")
print("Three dot character count: \(threeDotString.count)")
// The output:
Ellipsis character count: 11
Three dot character count: 13
As you can see, with the proper ellipsis character, it counts it as only 1 character
Now using your contains function with the ellipsis string:
var ellipsisString = "This is … a"
print(ellipsisString.contains("…"))
print(ellipsisString.contains("..."))
// The output
true
false
You can see contains("…") succeeds with the real ellipsis character but contains("...") fails with the three dots you used.
Finally, let's say I wanted to add the string nice after the ellipsis character in the ellipsis string This is … a - your strategy will not work of adding 2 to the index if a proper ellipsis character was used
Here is what I do to achieve this:
var ellipsisString = "This is … a"
// Find the index of the ellipsis character
if let ellipsisIndex = ellipsisString.firstIndex(of: "…")
{
// Convert the String index to a numeric value
let numericIndex: Int
= ellipsisString.distance(from: ellipsisString.startIndex,
to: ellipsisIndex)
// Ellipsis located at the 8th index which is right
print(numericIndex)
// Set the index where we want to add the new text
// The index after the ellipsis character so offset by 1
let insertIndex = ellipsisString.index(ellipsisIndex,
offsetBy: 1)
// Insert the text " nice" into the string after the
// ellipsis character
ellipsisString.insert(contentsOf: " nice",
at: insertIndex)
}
// Print the output after the addition
print(ellipsisString)
// Output
This is … nice a
This gives the desired output you wish which is finding the position of the ellipsis character accurately and then using that position to do what you want, in my case, adding a text after the ellipsis character.
Hope this clears some things up for you
Update With Example
Here is a small update to detect an ellipsis or a period added by the user in a UITextField in real time. The ellipsis will be highlighted in yellow
1. Set Up with a UITextField
class EllipsisViewController: UIViewController
{
let textField = UITextField()
override func viewDidLoad()
{
super.viewDidLoad()
// Basic set up
view.backgroundColor = .white
title = "Ellipses text"
configureTextField()
}
private func configureTextField()
{
// UITextField with auto layout
textField.layer.borderWidth = 2.0
textField.delegate = self
textField.layer.borderColor = UIColor.blue.cgColor
textField.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(textField)
textField.leadingAnchor
.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor,
constant: 16)
.isActive = true
textField.topAnchor
.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor)
.isActive = true
textField.trailingAnchor
.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor,
constant: -16)
.isActive = true
textField.heightAnchor
.constraint(equalToConstant: 70)
.isActive = true
}
}
Detect ellipsis in a string using UITextFieldDelegate and searching character by character
extension EllipsisViewController: UITextFieldDelegate
{
func textFieldDidChangeSelection(_ textField: UITextField)
{
highlightEllipsisAndPeriods()
}
private func highlightEllipsisAndPeriods()
{
if let text = textField.text
{
let attributedText = NSMutableAttributedString(string: text)
let ellipsisCharacter: String.Element = "…"
for (index, character) in text.enumerated()
{
let attributedKey = NSAttributedString.Key.backgroundColor
// Highlight ellipsis in yellow
if character == ellipsisCharacter
{
attributedText.addAttribute(attributedKey,
value: UIColor.yellow,
range: NSRange(location: index,
length: 1))
}
// Highlight periods in green
if character == "."
{
attributedText.addAttribute(attributedKey,
value: UIColor.green,
range: NSRange(location: index,
length: 1))
}
}
textField.attributedText = attributedText
}
}
}
This gives you the following result
If you have a single pattern you want to find like an ellipsis, you have the option to use NSRegularExpression as well
That would give the same result as above, here is the function:
extension EllipsisViewController: UITextFieldDelegate
{
func textFieldDidChangeSelection(_ textField: UITextField)
{
//highlightEllipsisAndPeriods()
highlightEllipsisUsingRegex()
}
private func highlightEllipsisUsingRegex()
{
if let text = textField.text
{
let attributedText = NSMutableAttributedString(string: text)
let ellipsisString = "…"
do
{
let regex = try NSRegularExpression(pattern: ellipsisString,
options: .caseInsensitive)
// Loop through all the ellipsis found and highlight
// in yellow
for match in regex.matches(in: text,
options: NSRegularExpression.MatchingOptions(),
range: NSRange(location: 0,
length: text.count))
as [NSTextCheckingResult]
{
attributedText.addAttribute(NSAttributedString.Key.backgroundColor,
value: UIColor.yellow,
range: match.range)
}
textField.attributedText = attributedText
}
catch
{
// handle errors
print("error")
}
}
}
}
This is the result using the Regex method:
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 |


