Voice over doesn't read phone number properly - ios

I have phone number in below format
1-1xx-2xx-9565
Currently VO read it as "One (pause) One x x (pause) two x x (pause) minus nine thousand five hundred sixty five".
VO should read it as "One (pause) One x x (pause) two x x (pause) nine five six five".
What could be the problem? Is this wrong phone format?

Let's break down what is happening. VoiceOver doesn't know that the text you are presenting is a phone number and treats it like a sentence of text. In that text it tries to find distinct components and read them appropriately. For example, the text "buy 60 cantaloupes" has 3 components, "buy", "60", and "cantaloupes". The first is text and is read as text, the second is purely numerical and is best read out as "sixty", and the third is read as text.
Applying the same logic to your phone number.
(I'm not talking about actual implementation, just reasoning.)
If you read 1-1xx-2xx-9565 from the left to the right then the first distinct component is "1" which in it self is numerical and is read as "1". If the phone number would have started with "12-1xx" then the first component would have been read as "twelve" because its purely numerical.
The next component is "1xx" or "-1xx" depending on how you look at it. In either case it is a combination of numbers and letters, e.g. it is not purely numerical and is thus read out as text. If you include the "-" in that component is interpreted as a hyphen which isn't read out. That is why the the "-" is never read out for that component. The next component ("-2xx") is treated in the same way.
The final component is "-9565" which turns out to be a valid number. As seen in the cantaloupe sentence, VoiceOver reads this as a number in which case the "-" is no longer interpreted as a hyphen but as a "minus sign".
Getting VoiceOver to read your own text
On any label, view or other element in your application that is used with Voice Over, you can supply your own "accessibility label" when you know more about how you want the text to be read. This is done by simply assigning your own string to the accessibilityLabel property.
Now, you can create a suitable string in many different ways, a very simple one in your case would be to just add spaces everywhere so that each number is read individually. However, it seems a bit fragile to me, so I went ahead and used a number formatter to translate the individual numbers to their textual representations.
NSString *phoneNumber = #"1-1xx-2xx-9565";
// we want to know if a character is a number or not
NSCharacterSet *numberCharacters = [NSCharacterSet characterSetWithCharactersInString:#"0123456789"];
// we use this formatter to spell out individual numbers
NSNumberFormatter *spellOutSingleNumber = [NSNumberFormatter new];
spellOutSingleNumber.numberStyle = NSNumberFormatterSpellOutStyle;
NSMutableArray *spelledOutComonents = [NSMutableArray array];
// loop over the phone number add add the accessible variants to the array
[phoneNumber enumerateSubstringsInRange:NSMakeRange(0, phoneNumber.length)
options:NSStringEnumerationByComposedCharacterSequences
usingBlock:^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop) {
// check if it's a number
if ([substring rangeOfCharacterFromSet:numberCharacters].location != NSNotFound) {
// is a number
NSNumber *number = #([substring integerValue]);
[spelledOutComonents addObject:[spellOutSingleNumber stringFromNumber:number]];
} else {
// is not a number
[spelledOutComonents addObject:substring];
}
}];
// finally separate the components with spaces (so that the string doesn't become "ninefivesixfive".
NSString *yourAccessiblePhoneNumber = [spelledOutComonents componentsJoinedByString:#" "];
The result when I ran this was
one - one x x - two x x - nine five six five
If you need to do other modifications to your phone numbers to get them to read appropriately then you can do that. I suspect that you will use this is more than one place in your app so creating a custom NSFormatter might be a good idea.
Edit
On iOS 7 you can also use the UIAccessibilitySpeechAttributePunctuation attribute on an attributes string to change how it is pronounced.
Speech Attributes for Attributed Strings
Attributes that you can apply to text in an attributed string to modify how that text is pronounced.
UIAccessibilitySpeechAttributePunctuation
The value of this key is an NSNumber object that you should interpret as a Boolean value. When the value is YES, all punctuation in the text is spoken. You might use this for code or other text where the punctuation is relevant.
Available in iOS 7.0 and later.
Declared in UIAccessibilityConstants.h.

As of iOS 13 you can use a - NSAttributedString.Key.accessibilitySpeechSpellOut as a accessibilityAttributedLabel to make VoiceOver read each letter of the provided string (or a range of string).
So for example:
yourView.accessibilityAttributedLabel = NSAttributedString(string: yourText, attributes: [.accessibilitySpeechSpellOut: true])

If you want to spell all characters individually, a simple solution is to separate the characters by a comma ",".
You can use a String extension to convert the string:
extension String
{
/// Returns string suitable for accessibility (voice over). All characters will be spelled individually.
func stringForSpelling() -> String
{
return stringBySeparatingCharactersWithString(",")
}
/// Inserts a separator between all characters
func stringBySeparatingCharactersWithString(separator: String) -> String
{
var s = ""
// Separate all characters
let chars = self.characters.map({ String($0) })
// Append all characters one by one
for char in chars {
// If there is already a character, append separator before appending next character
if s.characters.count > 0 {
s += separator
}
// Append next character
s += char
}
return s
}
}
And then use it in the code like this:
myLabel.accessibilityLabel = myString.stringForSpelling()

Just Add a comma to each digit of the last number and after the last digit as well,. this will make sure voice over reads the last number as same as previous numbers.
example your number :- 1-1xx-2xx-9565
accessibility label :- 1-1xx-2xx-9,5,6,5,

Here is the code in Swift
public func retrieveAccessiblePhoneNumber(phoneNumber: String) -> String {
// We want to know if a character is a number or not
let characterSet = NSCharacterSet(charactersInString: "0123456789")
// We use this formatter to spell out individual numbers
let numberFormatter = NSNumberFormatter()
numberFormatter.numberStyle = .SpellOutStyle
var spelledOutComponents = [String]()
let range = Range<String.Index>(start: phoneNumber.startIndex, end: phoneNumber.endIndex)
// Loop over the phone number add add the accessible variants to the array
phoneNumber.enumerateSubstringsInRange(range,
options: NSStringEnumerationOptions.ByComposedCharacterSequences) { (substring, substringRange, enclosingRange, stop) -> () in
// Check if it's a number
if let substr = substring where substr.rangeOfCharacterFromSet(characterSet) != nil {
if let number = Int(substr) {
// Is a number
let nsNumber = NSNumber(integer: number)
spelledOutComponents.append(numberFormatter.stringFromNumber(nsNumber)!)
}
} else {
// Is not a number
spelledOutComponents.append(substring!)
}
}
// Finally separate the components with spaces (so that the string doesn't become "ninefivesixfive".
return spelledOutComponents.joinWithSeparator(" ")
}

Related

NSRegularExpression gives incorrect matches for pattern

Goal : Multiple splits of the string, Each must have 50 characters. It would be really great if I can do it by proper word wrapping. It is okay to have the last split as less than 50 characters.
I am using this method to split a String into multiple parts. I have used a regular expression for this, which is given below.
"\\G\\s*(.{1,50})(?=\\s|$)"
And used this NSURLRegularExpression to find the corresponding splits.
extension String {
func getSplits()
{
let pattern = try? NSRegularExpression(pattern: "\\G\\s*(.{1,50})(?=\\s|$)", options: NSRegularExpressionOptions.DotMatchesLineSeparators)
if let matches = pattern?.matchesInString(self, options: NSMatchingOptions.ReportCompletion , range: NSMakeRange(0, self.characters.count)) as [NSTextCheckingResult]?
{
for (index, result) in matches.enumerate()
{
// Statements
}
}
}
}
But the problem is that, whenever I put English text it returns me the proper splits. But whenever I put text other than English, it seems to be showing incorrect splits. As in each split must have 50 characters right? But number of characters in the output are sometimes 5 or 10 or some random number. Randomly splitting, I have a doubt? Is there anything to do with the \n character or \r? No right?
I have tried all option types of NSRegularExpressionOptions and NSMatchingOptions. Even no options, but same thing happened.
If you are testing, this string will help you, test it with this Malayalam Language text ->
കുളച്ചല്‍ തുറമുഖം നിര്‍മ്മിക്കാനുള്ള നീക്കം യുക്തിരഹിത പദ്ധതിയെന്നാണ് മുഖ്യമന്ത്രി പിണറായി വിജയന്‍ മറുപടി നല്‍കിയത്. പ്രധാനമന്ത്രിയെ നേരില്‍ കണ്ട് ഇക്...
പൊതുജനങ്ങളുടെ നികുതിപ്പണം യുക്തിരഹിതമായി ഉപയോഗിക്കുന്നതിനുള്ള തെളിവാണ് കുളച്ചല്‍. 1000 ദിവസം കൊണ്ട് മുന്‍നിശ്ചയപ്രകാരം പദ്ധതി പൂര്‍ത്തിയാക്കും. തുറമുഖ...
Do you have any thoughts on this?

How to extract emojis from a string?

I'm looking for a way to take a String and extract Emoji characters.
I know that Emojis are part of Unicode so I need to remove a certain subset of Unicode characters. I don't really know where to start.
The Set of Emoji characters
First of all we need a Set containing the unicode values representing the emoji.
Disclaimer
For this answer I am using the range of Emoticons (1F601-1F64F) and Dingbats (2702-27B0) to show you the solution. However keep in mind that you should add further ranges depending on your needs.
Extending Character
Now we need a way to calculate the Unicode Scalar Code Point of a Character. For this I am using the solution provided here.
extension Character {
private var unicodeScalarCodePoint: Int {
let characterString = String(self)
let scalars = characterString.unicodeScalars
return Int(scalars[scalars.startIndex].value)
}
}
Extending String
This extension does allow you to extract the emoji characters from a String.
extension String {
var emojis:[Character] {
let emojiRanges = [0x1F601...0x1F64F, 0x2702...0x27B0]
let emojiSet = Set(emojiRanges.flatten())
return self.characters.filter { emojiSet.contains($0.unicodeScalarCodePoint) }
}
}
Testing
let sentence = "😃 hello world 🙃"
sentence.emojis // ["😃", "🙃"]

How to separate emojis entered (through default keyboard) on textfield

I entered a two emojis in textfield 👨‍👨‍👧‍👧😍, here I'm getting total number of 5 characters length whereas 4 characters for first emoji and 1 character for second. Looks like apple has combined 4 emojis to form a one.
I'm looking for the swift code where I can separate each of emojis separately, suppose by taking the above example I should be getting 2 strings/character separately for each emoji.
Can any one help me to solve this, I've tried many things like regex separation or componentsSeparatedByString or characterSet. but unfortunately ended up with negative.
Thanks in advance.
Update for Swift 4 (Xcode 9)
As of Swift 4 (tested with Xcode 9 beta) a "Emoji ZWJ Sequence" is
treated as a single Character as mandated by the Unicode 9 standard:
let str = "👨‍👨‍👧‍👧😍"
print(str.count) // 2
print(Array(str)) // ["👨‍👨‍👧‍👧", "😍"]
Also String is a collection of its characters (again), so we can
call str.count to get the length, and Array(str) to get all
characters as an array.
(Old answer for Swift 3 and earlier)
This is only a partial answer which may help in this particular case.
"👨‍👨‍👧‍👧" is indeed a combination of four separate characters:
let str = "👨‍👨‍👧‍👧😍" //
print(Array(str.characters))
// Output: ["👨‍", "👨‍", "👧‍", "👧", "😍"]
which are glued together with U+200D (ZERO WIDTH JOINER):
for c in str.unicodeScalars {
print(String(c.value, radix: 16))
}
/* Output:
1f468
200d
1f468
200d
1f467
200d
1f467
1f60d
*/
Enumerating the string with the .ByComposedCharacterSequences
options combines these characters correctly:
var chars : [String] = []
str.enumerateSubstringsInRange(str.characters.indices, options: .ByComposedCharacterSequences) {
(substring, _, _, _) -> () in
chars.append(substring!)
}
print(chars)
// Output: ["👨‍👨‍👧‍👧", "😍"]
But there are other cases where this does not work,
e.g. the "flags" which are a sequence of "Regional Indicator
characters" (compare Swift countElements() return incorrect value when count flag emoji). With
let str = "🇩🇪"
the result of the above loop is
["🇩", "🇪"]
which is not the desired result.
The full rules are defined in "3 Grapheme Cluster Boundaries"
in the "Standard Annex #29 UNICODE TEXT SEGMENTATION" in the
Unicode standard.
You can use this code example or this pod.
To use it in Swift, import the category into the YourProject_Bridging_Header
#import "NSString+EMOEmoji.h"
Then you can check the range for every emoji in your String:
let example: NSString = "👨‍👨‍👧‍👧😍" // your string
let ranges: NSArray = example.emo_emojiRanges() // ranges of the emojis
for value in ranges {
let range:NSRange = (value as! NSValue).rangeValue
print(example.substringWithRange(range))
}
// Output: ["👨‍👨‍👧‍👧", "😍"]
I created an small example project with the code above.
For further reading, this interesting article from Instagram.

String characters (marks) are not counted correctlly

Characters as (é) or in arabic (دٌ) are counted as one in a string, how do I make it recognise the mark as a character?
It should be like (د) is a character and (ٌ) is another character.
I don't want to use NSString because I'm using (startIndex) which is not supported in NSString as far as I know.
Thank you
I’m by no means sufficiently knowledgeable in this area to be confident there aren’t some gotchas from this approach, but this appears to do what you’re looking for:
let s = "éدٌ"
let separated = map(s.unicodeScalars) { Character($0) }
println(" , ".join(separated.map(toString)))
// prints "e , ́ , د , ٌ"
Note, if you create a new string from a sequence of those separated characters, it will recompose them:
println(String(separated)) // prints
// prints "éدٌ"

Getting the First Letter of a String in Hebrew

In a UITableView, I'm listing a bunch of languages to be selected. And to put a section index view to the right like in Contacts app, I'm getting all first letters of languages in the list and then use it to generate the section index view.
It works almost perfect, Just I encountered with a problem in getting first letter of some strings in Hebrew. Here a screenshot from playground, one of the language name that I couldn't get the first letter:
Problem is, the first letter of the name of the language that has "ina" language code, isn't "א", it's an empty character; it's not a space, it's just an empty character. As you can see, it's actually 12 characters in total, but when I get count of it, it says 13 characters because there is an non-space empty character in index 0.
It works perfectly if I use "eng" or "ara" languages with putting these values in value: parameter. So maybe the problem is cause of system that returns a language name with an empty character in some cases, I don't know.
I tried some different methods of getting first letter, but any of it didn't work.
Here "א" isn't the first letter, it's the second letter. So I thought maybe I can find a simple hack with that, but I want to try solving it before trying workarounds.
Here is the code:
let locale = NSLocale(localeIdentifier: "he")
let languageName = locale.displayNameForKey(NSLocaleIdentifier, value: "ina")!
let firstLetter = first(languageName)!
println(countElements(languageName))
for character in languageName {
println(character)
}
You could use an NSCharacterSet.controlCharacterSet() to test each character. I can't figure out how to stay in Swift-native strings, but here's a function that uses NSString to return the first non-control character:
func firstNonControlCharacter(str: NSString) -> String? {
let controlChars = NSCharacterSet.controlCharacterSet()
for i in 0..<str.length {
if !controlChars.characterIsMember(str.characterAtIndex(i)) {
return str.substringWithRange(NSRange(location: i, length: 1))
}
}
return nil
}
let locale = NSLocale(localeIdentifier: "he")
let languageName = locale.displayNameForKey(NSLocaleIdentifier, value: "ina")!
let firstChar = firstNonControlCharacter(languageName) // Optional("א")

Resources