How can I prevent iOS from automatically underlining numbers in Swift? - ios

I'm building a side project to play around with iOS development and decided it would be a messaging app. I'm adding timestamps to the messages below the message body, but still within the bubble, as I like this look better than outside of the bubble. iOS automatically formats these numbers for inclusion in calendar. Is there a way to escape this formatting for JUST those numbers? I'd like to keep it for when users enter times and dates, as that's really useful.
Below is the block that's adding the message body, as well as a screenshot of what I'm referring to.
override func didPressSendButton(button: UIButton!, withMessageText text: String!, senderId: String!, senderDisplayName: String!, date: NSDate!) {
JSQSystemSoundPlayer.jsq_playMessageSentSound()
let date = NSDate()
let calendar = NSCalendar.currentCalendar()
let components = calendar.components(.CalendarUnitHour | .CalendarUnitMinute, fromDate: date)
let hour = components.hour
var minutes = components.minute
if minutes < 10 {
var minutes = String(minutes)
minutes = String(format: "%02d", minutes)
}
var newText = text + "\n\n \(hour):\(minutes)"
var newMessage = JSQMessage(senderId: senderId, displayName: senderDisplayName, text: newText);
messages += [newMessage]
self.finishSendingMessage()
}

In the JSQMessagesViewController where the cell is getting created, the data detector type is set to all.
Line 539 of JSQMessagesViewController.m
cell.textView.dataDetectorTypes = UIDataDetectorTypeAll;
cell.backgroundColor = [UIColor clearColor];
cell.layer.rasterizationScale = [UIScreen mainScreen].scale;
cell.layer.shouldRasterize = YES;
return cell;
You can just set it to UIDataDetectorTypeNone.

You have the dataDetector enabled, so it's detecting URLs, phone numbers, dates, and so on, and turning them into links.
Disable all or specific data detection, depending on your needs.
In your specific case, it sounds like you'd want to disable the UIDataDetectorTypeCalendarEvent data detection.
Update:
Here's a possible answer. Format the text so it doesn't appear to be a time. It may be possible, for example, to use a unicode character for the newText colon which the data detector won't catch as a time. Another option is to use a zero-width space to separate the hours with an 'invisible' character.
var newText = text + "\n\n \(hour)\u{200b}:\(minutes)" // zero-width space
Update 2:
I downloaded JSQMessage and tried this in the sample code. A zero-width space before the colon does appear to work to avoid the time from being detected.
[[JSQMessage alloc] initWithSenderId:kJSQDemoAvatarIdSquires
senderDisplayName:kJSQDemoAvatarDisplayNameSquires
date:[NSDate distantPast]
text:#"It even has data detectors. You can call me tonight. My cell number is 123-456-7890. My website is www.hexedbits.com.\n\n9\u200b:41"],

In Swift 3: To disable all detections
cell.textView?.dataDetectorTypes = []

Related

Getting numbers in word format in SFSpeechRecognizer instead of numbers

Is there any way of printing numbers into proper spellings instead of throwing numbers while recording voice via SFSpeechRecognizer? I've tried to get the word format by implementing the code below:
if let resultString = result?.bestTranscription.formattedString {
if let number = Double(resultString) {
let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = .spellOut
let numberString = numberFormatter.string(from: NSNumber(value: number))
let numberStringWithoutHyphen = numberString?.replacingOccurrences(of: "-", with: " ")
print(numberStringWithoutHyphen)
}
}
This solution works great if the user is speaking whole numbers or even decimal numbers but there are some cases where this solution doesn't work at all and makes this solution look dumb. For example, if the user says "Fifty five point zero", the speech recognizer picks it up as "55.0". But the number formatter returns "Fifty five". In an extreme case, if the user says "One two three four", the speech recognizer picks it up as "1234" but the number formatter returns "One thousand two hundred thirty four".
What I am aiming for is if the user says any number, the speech recognizer should return the same, word by word. If the user says "Fifty five point zero", it should return "Fifty five point zero". If the user says "One two three four", it should return "One two three four".

RelativeDateTimeFormatter Context for Localised String

I have a RelativeDateTimeFormatter that I'm using to sho strings like this:
"Next lottery draw in 2 days"
"Next lottery draw tomorrow"
This comes from the strings file with string format like this "Next lottery draw %#"
My problem is how do I localise this properly when the %# is in a different place in the string. For example, in Japanese the %# is somewhere in the middle of the string.
Here's my code for setting up the date formatter.
let exampleDate = Date().addingTimeInterval(-15000)
let formatter = RelativeDateTimeFormatter()
formatter.context = .dynamic
let relativeDate = formatter.localizedString(for: exampleDate, relativeTo: Date())
let myString = String(format: NSLocalizedString("string_key", comment: ""), relativeDate)
Formatters have a context you can pass to tell the formatter where the string's going to be used.
https://developer.apple.com/documentation/foundation/formatter/context
And one of the cases is called dynamic which says that it's going to determine it at runtime. However, I can't see how that works or how I'm supposed to use the RelativeDateTimeFormatter with that.

Migrating ObjC NSNumber code to Swift and having problems with displaying numbers.

I am migrating my ObjC app to Swift and have run into a peculiar issue.
My app reads a JSON file and builds a model from it. The numbers read from JSON used to be assigned to NSNumber. Some numbers were whole (eg.60) other had a decimal component (e.g. 60.23).
When I displayed these numbers in my app, 60 would be 60 (not 60.0) and decimals would display appropriately.
Now I am writing Swift code and assigning the numbers read from JSON to Double. Even if I use let x=String(myDouble) the number 60 will always come out 60.0
I don't want this...but I can't just format it %f because I never know if the number will be whole or have a decimal component.
Do I really need to check if it is a whole and then format it using it %f?
Am I missing something in Swift? The ObjC code behaved as I wanted by now Swift seems to be giving me decimals for whole numbers.
You could use NSNumberFormatter.
let number1 : Double = 60
let number2 = 60.23
let numberFormatter = NSNumberFormatter()
numberFormatter.numberStyle = .DecimalStyle
let number1String = numberFormatter.stringFromNumber(number1) // "60"
let number2String = numberFormatter.stringFromNumber(number2) // "60.23"
FYI, here is the code in PlayGround:
let number = 60.23
String(number)
let double:Double = 60
//First way
String(NSNumberFormatter().stringFromNumber(double)!)
//Second way
let doubleString = String(double)
if doubleString.containsString(".0") {
let length = doubleString.characters.count
let index = doubleString.characters.indexOf(".")
doubleString.substringToIndex(index!)
}

Using NSDataDetector to just like Apple's Notes app

I'm trying to find several different data types including Dates, Addresses, Phone numbers, and Links. I'm already able to find them but I want to be able to format them by underlining and changing their color. This is my code so far.
func detectData() {
let text = self.textView.text
let types: NSTextCheckingType = .Date | .Address | .PhoneNumber | .Link
var error: NSError?
let detector = NSDataDetector(types: types.rawValue, error: &error)
var dataMatches: NSArray = [detector!.matchesInString(text, options: nil, range: NSMakeRange(0, (text as NSString).length))]
for match in dataMatches {
I was thinking I should first get each result out of the loop then
1) turn them into strings 2)format them.
First question. How will I put my formatted string back into my UITextView at the same place?
Second question. I'm thinking about creating a switch like so
switch match {
case match == NSTextCheckingType.date
but now that I have a specific type of NSTextCheckingType, what do I have to do to make them have the functionality I want? (e.g. call a phone number, open up maps for an address, create a event for a date)
To do what Notes does you just need to set the dataDetectorTypes property on your text view. That's all! No NSDataDetector involved.

Tesseract in iOS (Swift) - How to Separate Text and Numbers in UITextField?

I have a Swift-based application that currently implements the Tesseract OCR framework (similar to the form in this tutorial: http://www.raywenderlich.com/93276/implementing-tesseract-ocr-ios). So upon taking a picture and employing Tesseract, I obtain the following output in a UITextField object:
Subtotal 155.60
Tax 14.02
Total 169.82
So now I would like to separate the text from the numbers in the UITextField. I was considering using the "contain" function built into Swift on a matrix containing all values in price format ([0.01 0.02, etc.]) but this will only return a boolean as outlined in this post (How to have a textfield scan for all values in an array individually in swift?). Does anyone have any suggestions on how to do this? Cheers!
Tesseract Implementation
func performImageRecognition(image: UIImage)
// 0
// 1
let tesseract = G8Tesseract()
// 2
tesseract.language = "eng"
// 3
tesseract.engineMode = .TesseractCubeCombined
// 4
tesseract.pageSegmentationMode = .Auto
// 5
tesseract.maximumRecognitionTime = 60.0
// 6
tesseract.image = image.g8_blackAndWhite()
tesseract.recognize()
// 7
textView.text = tesseract.recognizedText
textView.editable = true
Sounds like you might want to look into using Regular Expressions
func seperate (text: String) -> (text: String?, value: String?) {
// You might want to do an extra check here to ensure the whole string is valid
// i.e., nothing in between the two parts of the string
let textMatch = text.rangeOfString("^([A-Z]|[a-z])+", options: .RegularExpressionSearch)
let priceMatch = text.rangeOfString("[0-9]*.[0-9]{2}$", options: .RegularExpressionSearch)
// You might want to adjust regex to handle price edge cases, such as 15 (rather than 15.00) etc
if let textMatch = textMatch, priceMatch = priceMatch {
let textValue = text.substringWithRange(textMatch)
let priceValue = text.substringWithRange(priceMatch)
return(textValue, priceValue)
} else {
return (nil, nil)
}
}
seperate("Subtotal 155.60") // -> Subtotal, 155.60

Resources