How to replace emoji characters with their descriptions in a Swift string - xcode

I'm looking for a way to replace emoji characters with their description in a Swift string.
Example:
Input "This is my string 😄"
I'd like to replace the 😄 to get:
Output "This is my string {SMILING FACE WITH OPEN MOUTH AND SMILING EYES}"
To date I'm using this code modified from the original code of this answer by MartinR, but it works only if I deal with a single character.
let myCharacter : Character = "😄"
let cfstr = NSMutableString(string: String(myCharacter)) as CFMutableString
var range = CFRangeMake(0, CFStringGetLength(cfstr))
CFStringTransform(cfstr, &range, kCFStringTransformToUnicodeName, Bool(0))
var newStr = "\(cfstr)"
// removing "\N" from the result: \N{SMILING FACE WITH OPEN MOUTH AND SMILING EYES}
newStr = newStr.stringByReplacingOccurrencesOfString("\\N", withString:"")
print("\(newStr)") // {SMILING FACE WITH OPEN MOUTH AND SMILING EYES}
How can I achieve this?

Simply do not use a Character in the first place but use a String as input:
let cfstr = NSMutableString(string: "This 😄 is my string 😄") as CFMutableString
that will finally output
This {SMILING FACE WITH OPEN MOUTH AND SMILING EYES} is my string {SMILING FACE WITH OPEN MOUTH AND SMILING EYES}
Put together:
func transformUnicode(input : String) -> String {
let cfstr = NSMutableString(string: input) as CFMutableString
var range = CFRangeMake(0, CFStringGetLength(cfstr))
CFStringTransform(cfstr, &range, kCFStringTransformToUnicodeName, Bool(0))
let newStr = "\(cfstr)"
return newStr.stringByReplacingOccurrencesOfString("\\N", withString:"")
}
transformUnicode("This 😄 is my string 😄")

Here is a complete implementation.
It avoids to convert to description also the non-emoji characters (e.g. it avoids to convert “ to {LEFT DOUBLE QUOTATION MARK}). To accomplish this, it uses an extension based on this answer by Arnold that returns true or false whether a string contains an emoji.
The other part of the code is based on this answer by MartinR and the answer and comments to this answer by luk2302.
var str = "Hello World 😄 …" // our string (with an emoji and a horizontal ellipsis)
let newStr = str.characters.reduce("") { // loop through str individual characters
var item = "\($1)" // string with the current char
let isEmoji = item.containsEmoji // true or false
if isEmoji {
item = item.stringByApplyingTransform(String(kCFStringTransformToUnicodeName), reverse: false)!
}
return $0 + item
}.stringByReplacingOccurrencesOfString("\\N", withString:"") // strips "\N"
extension String {
var containsEmoji: Bool {
for scalar in unicodeScalars {
switch scalar.value {
case 0x1F600...0x1F64F, // Emoticons
0x1F300...0x1F5FF, // Misc Symbols and Pictographs
0x1F680...0x1F6FF, // Transport and Map
0x2600...0x26FF, // Misc symbols
0x2700...0x27BF, // Dingbats
0xFE00...0xFE0F, // Variation Selectors
0x1F900...0x1F9FF: // Various (e.g. 🤖)
return true
default:
continue
}
}
return false
}
}
print (newStr) // Hello World {SMILING FACE WITH OPEN MOUTH AND SMILING EYES} …
Please note that some emoji could not be included in the ranges of this code, so you should check if all the emoji are converted at the time you will implement the code.

Related

Regular expression

I want the user to enter text in a text field, and if the user types "<" a space should be automatically appended to the text in the field
I tried removing the special character but I need the user to input that as well.
let RISTRICTED_CHARACTERS = "<"
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let set = CharacterSet(charactersIn: RISTRICTED_CHARACTERS)
let inverted = set.inverted
let filtered = string.components(separatedBy: inverted).joined(separator: "")
if filtered == string && string != "" {
return false
} else {
let maxLength = maxLenghtOfTextField
let currentString: NSString = textField.text! as NSString
let newString: NSString = currentString.replacingCharacters(in: range, with: string) as NSString
return newString.length <= maxLength
}
In this code I'm not allowing "<" this character. I want the text field to be like this.
My output should be : hello <(space) world.
The space should be automatically appended if I start with "<" sign.
Instead of .replacingCharacters maybe try .replacingOccurences
let updatedString: String? = textField.text.replacingOccurrences(of: "<", with: " ")

Logging in Swift 2.0

I am new to XCode and work on Android Studio previously. In Android Studio, there is log cat to log different types of messages for debugging purposes.
Is this available in XCode?
All I found is NSLog which prints the date and the statement without coloring like in log cat. Is there an easier way ?
You can use the print method.
Check out these handy Apple docs.
https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/TheBasics.html
using XCodeColors Library https://github.com/robbiehanson/XcodeColors you can log different types of messages each in a unique color so that you can find error logs faster
also i customized the code like this to get coloring, which class, function, and line number did the call
struct RZLog
{
static let ESCAPE = "\u{001b}["
static let RESET_FG = ESCAPE + "fg;" // Clear any foreground color
static let RESET_BG = ESCAPE + "bg;" // Clear any background color
static let RESET = ESCAPE + ";" // Clear any foreground or background color
static let A = "fg255,0,0;"
static let B = "fg0,0,255;"
static let C = "fg16,128,0;"
static func Error<T>(object: T, filename: String = FILE, line: Int = LINE, funcname: String = FUNCTION) {
let ClassName = NSURL(string: filename)!
print("\(ESCAPE)\(A)**ERROR \(ClassName.lastPathComponent!)(\(line)) Func: \(funcname.uppercaseString): \(object) **\(RESET)")
}
static func Debug<T>(object: T, filename: String = FILE, line: Int = LINE, funcname: String = FUNCTION) {
let ClassName = NSURL(string: filename)!
print("\(ESCAPE)\(B)**DEBUG \(ClassName.lastPathComponent!)(\(line)) Func: \(funcname.uppercaseString): \(object) **\(RESET)")
}
static func VIP<T>(object: T, filename: String = FILE, line: Int = LINE, funcname: String = FUNCTION) {
let ClassName = NSURL(string: filename)!
print("\(ESCAPE)\(C)**VIP \(ClassName.lastPathComponent!)(\(line)) Func: \(funcname.uppercaseString): \(object) **\(RESET)")
}
}
If you want to use different CocoaLumberjack:
https://github.com/CocoaLumberjack/CocoaLumberjack
Which provides some more advantages over simple logging. And it can also be used with colors:
http://code.tutsplus.com/tutorials/cocoalumberjack-logging-on-steroids--mobile-15287
You can use Printer a new logging experience in Swift 3.x.
It has many functions to add logs in various ways.
Usage:
To log a success message:
Printer.log.success(details: "This is a Success message.")
Output:
Printer ➞ [✅ Success] [⌚04-27-2017 10:53:28] ➞ ✹✹This is a Success message.✹✹
[Trace] ➞ ViewController.swift ➞ viewDidLoad() #58
Disclaimer: This library has been created by me.

Decoding quoted-printable messages in Swift

I have a quoted-printable string such as "The cost would be =C2=A31,000". How do I convert this to "The cost would be £1,000".
I'm just converting text manually at the moment and this doesn't cover all cases. I'm sure there is just one line of code that will help with this.
Here is my code:
func decodeUTF8(message: String) -> String
{
var newMessage = message.stringByReplacingOccurrencesOfString("=2E", withString: ".", options: NSStringCompareOptions.LiteralSearch, range: nil)
newMessage = newMessage.stringByReplacingOccurrencesOfString("=E2=80=A2", withString: "•", options: NSStringCompareOptions.LiteralSearch, range: nil)
newMessage = newMessage.stringByReplacingOccurrencesOfString("=C2=A3", withString: "£", options: NSStringCompareOptions.LiteralSearch, range: nil)
newMessage = newMessage.stringByReplacingOccurrencesOfString("=A3", withString: "£", options: NSStringCompareOptions.LiteralSearch, range: nil)
newMessage = newMessage.stringByReplacingOccurrencesOfString("=E2=80=9C", withString: "\"", options: NSStringCompareOptions.LiteralSearch, range: nil)
newMessage = newMessage.stringByReplacingOccurrencesOfString("=E2=80=A6", withString: "…", options: NSStringCompareOptions.LiteralSearch, range: nil)
newMessage = newMessage.stringByReplacingOccurrencesOfString("=E2=80=9D", withString: "\"", options: NSStringCompareOptions.LiteralSearch, range: nil)
newMessage = newMessage.stringByReplacingOccurrencesOfString("=92", withString: "'", options: NSStringCompareOptions.LiteralSearch, range: nil)
newMessage = newMessage.stringByReplacingOccurrencesOfString("=3D", withString: "=", options: NSStringCompareOptions.LiteralSearch, range: nil)
newMessage = newMessage.stringByReplacingOccurrencesOfString("=20", withString: "", options: NSStringCompareOptions.LiteralSearch, range: nil)
newMessage = newMessage.stringByReplacingOccurrencesOfString("=E2=80=99", withString: "'", options: NSStringCompareOptions.LiteralSearch, range: nil)
return newMessage
}
Thanks
An easy way would be to utilize the (NS)String method
stringByRemovingPercentEncoding for this purpose.
This was observed in
decoding quoted-printables,
so the first solution is mainly a translation of the answers in
that thread to Swift.
The idea is to replace the quoted-printable "=NN" encoding by the
percent encoding "%NN" and then use the existing method to remove
the percent encoding.
Continuation lines are handled separately.
Also, percent characters in the input string must be encoded first,
otherwise they would be treated as the leading character in a percent
encoding.
func decodeQuotedPrintable(message : String) -> String? {
return message
.stringByReplacingOccurrencesOfString("=\r\n", withString: "")
.stringByReplacingOccurrencesOfString("=\n", withString: "")
.stringByReplacingOccurrencesOfString("%", withString: "%25")
.stringByReplacingOccurrencesOfString("=", withString: "%")
.stringByRemovingPercentEncoding
}
The function returns an optional string which is nil for invalid input.
Invalid input can be:
A "=" character which is not followed by two hexadecimal digits,
e.g. "=XX".
A "=NN" sequence which does not decode to a valid UTF-8 sequence,
e.g. "=E2=64".
Examples:
if let decoded = decodeQuotedPrintable("=C2=A31,000") {
print(decoded) // £1,000
}
if let decoded = decodeQuotedPrintable("=E2=80=9CHello =E2=80=A6 world!=E2=80=9D") {
print(decoded) // “Hello … world!”
}
Update 1: The above code assumes that the message uses the UTF-8
encoding for quoting non-ASCII characters, as in most of your examples: C2 A3 is the UTF-8 encoding for "£", E2 80 A4 is the UTF-8 encoding for ….
If the input is "Rub=E9n" then the message is using the
Windows-1252 encoding.
To decode that correctly, you have to replace
.stringByRemovingPercentEncoding
by
.stringByReplacingPercentEscapesUsingEncoding(NSWindowsCP1252StringEncoding)
There are also ways to detect the encoding from a "Content-Type"
header field, compare e.g. https://stackoverflow.com/a/32051684/1187415.
Update 2: The stringByReplacingPercentEscapesUsingEncoding
method is marked as deprecated, so the above code will always generate
a compiler warning. Unfortunately, it seems that no alternative method
has been provided by Apple.
So here is a new, completely self-contained decoding method which
does not cause any compiler warning. This time I have written it
as an extension method for String. Explaining comments are in the
code.
extension String {
/// Returns a new string made by removing in the `String` all "soft line
/// breaks" and replacing all quoted-printable escape sequences with the
/// matching characters as determined by a given encoding.
/// - parameter encoding: A string encoding. The default is UTF-8.
/// - returns: The decoded string, or `nil` for invalid input.
func decodeQuotedPrintable(encoding enc : NSStringEncoding = NSUTF8StringEncoding) -> String? {
// Handle soft line breaks, then replace quoted-printable escape sequences.
return self
.stringByReplacingOccurrencesOfString("=\r\n", withString: "")
.stringByReplacingOccurrencesOfString("=\n", withString: "")
.decodeQuotedPrintableSequences(enc)
}
/// Helper function doing the real work.
/// Decode all "=HH" sequences with respect to the given encoding.
private func decodeQuotedPrintableSequences(enc : NSStringEncoding) -> String? {
var result = ""
var position = startIndex
// Find the next "=" and copy characters preceding it to the result:
while let range = rangeOfString("=", range: position ..< endIndex) {
result.appendContentsOf(self[position ..< range.startIndex])
position = range.startIndex
// Decode one or more successive "=HH" sequences to a byte array:
let bytes = NSMutableData()
repeat {
let hexCode = self[position.advancedBy(1) ..< position.advancedBy(3, limit: endIndex)]
if hexCode.characters.count < 2 {
return nil // Incomplete hex code
}
guard var byte = UInt8(hexCode, radix: 16) else {
return nil // Invalid hex code
}
bytes.appendBytes(&byte, length: 1)
position = position.advancedBy(3)
} while position != endIndex && self[position] == "="
// Convert the byte array to a string, and append it to the result:
guard let dec = String(data: bytes, encoding: enc) else {
return nil // Decoded bytes not valid in the given encoding
}
result.appendContentsOf(dec)
}
// Copy remaining characters to the result:
result.appendContentsOf(self[position ..< endIndex])
return result
}
}
Example usage:
if let decoded = "=C2=A31,000".decodeQuotedPrintable() {
print(decoded) // £1,000
}
if let decoded = "=E2=80=9CHello =E2=80=A6 world!=E2=80=9D".decodeQuotedPrintable() {
print(decoded) // “Hello … world!”
}
if let decoded = "Rub=E9n".decodeQuotedPrintable(encoding: NSWindowsCP1252StringEncoding) {
print(decoded) // Rubén
}
Update for Swift 4 (and later):
extension String {
/// Returns a new string made by removing in the `String` all "soft line
/// breaks" and replacing all quoted-printable escape sequences with the
/// matching characters as determined by a given encoding.
/// - parameter encoding: A string encoding. The default is UTF-8.
/// - returns: The decoded string, or `nil` for invalid input.
func decodeQuotedPrintable(encoding enc : String.Encoding = .utf8) -> String? {
// Handle soft line breaks, then replace quoted-printable escape sequences.
return self
.replacingOccurrences(of: "=\r\n", with: "")
.replacingOccurrences(of: "=\n", with: "")
.decodeQuotedPrintableSequences(encoding: enc)
}
/// Helper function doing the real work.
/// Decode all "=HH" sequences with respect to the given encoding.
private func decodeQuotedPrintableSequences(encoding enc : String.Encoding) -> String? {
var result = ""
var position = startIndex
// Find the next "=" and copy characters preceding it to the result:
while let range = range(of: "=", range: position..<endIndex) {
result.append(contentsOf: self[position ..< range.lowerBound])
position = range.lowerBound
// Decode one or more successive "=HH" sequences to a byte array:
var bytes = Data()
repeat {
let hexCode = self[position...].dropFirst().prefix(2)
if hexCode.count < 2 {
return nil // Incomplete hex code
}
guard let byte = UInt8(hexCode, radix: 16) else {
return nil // Invalid hex code
}
bytes.append(byte)
position = index(position, offsetBy: 3)
} while position != endIndex && self[position] == "="
// Convert the byte array to a string, and append it to the result:
guard let dec = String(data: bytes, encoding: enc) else {
return nil // Decoded bytes not valid in the given encoding
}
result.append(contentsOf: dec)
}
// Copy remaining characters to the result:
result.append(contentsOf: self[position ..< endIndex])
return result
}
}
Example usage:
if let decoded = "=C2=A31,000".decodeQuotedPrintable() {
print(decoded) // £1,000
}
if let decoded = "=E2=80=9CHello =E2=80=A6 world!=E2=80=9D".decodeQuotedPrintable() {
print(decoded) // “Hello … world!”
}
if let decoded = "Rub=E9n".decodeQuotedPrintable(encoding: .windowsCP1252) {
print(decoded) // Rubén
}
This encoding is called 'quoted-printable', and what you need to do is convert string to NSData using ASCII encoding, then just iterate over the data replacing all 3-symbol parties like '=A3' with the byte/char 0xA3, and then converting the resulting data to string using NSUTF8StringEncoding.
Unfortunately, I'm a bit late with my answer. It might be helpful for the others though.
var string = "The cost would be =C2=A31,000"
var finalString: String? = nil
if let regEx = try? NSRegularExpression(pattern: "={1}?([a-f0-9]{2}?)", options: NSRegularExpressionOptions.CaseInsensitive)
{
let intermediatePercentEscapedString = regEx.stringByReplacingMatchesInString(string, options: NSMatchingOptions.WithTransparentBounds, range: NSMakeRange(0, string.characters.count), withTemplate: "%$1")
print(intermediatePercentEscapedString)
finalString = intermediatePercentEscapedString.stringByRemovingPercentEncoding
print(finalString)
}
In order to give an applicable solution, a few more information is required. So, I will make some assumptions.
In an HTML or Mail message for example, you can apply one or more encodings to some kind of source data. For example, you could encode a binary file e.g. an png file with base64 and then zip it. The order is important.
In your example as you say, the source data is a String and has been encoded via UTF-8.
In a HTPP message, your Content-Type is thus text/plain; charset = UTF-8. In your example there seems also an additional encoding applied,
a "Content-Transfer-Encoding": possibly Content-transfer-encoding is quoted-printable or base64 (not sure about that, though).
In order to revert it back, you would need to apply the corresponding decodings in reverse order.
Hint:
You can view the headers (Contente-type and Content-Transfer-Encoding) of a mail message when viewing the raw source of the mail.
You can also look at this working solution - https://github.com/dunkelstern/QuotedPrintable
let result = QuotedPrintable.decode(string: quoted)

Swift adding multiple stringByReplacingOccurrencesOfString on one String?

Hello i would like to create a app that changes characters into binary code and i was wondering if there is a way to add multiple stringByReplacingOccurrencesOfString on one String or if i should take another approach to this "Problem".
Here is what i have so far
func textToBinary(theString: String) -> String {
return theString.stringByReplacingOccurrencesOfString("a",
withString: "01100001")
}
textArea.text = textToBinary(lettersCombined)
// lettersCombined is the string that i want to turn into BinaryCode.
Try this:
func textToBinary(theString : String, radix : Int = 2) -> String {
var result = ""
for c in theString.unicodeScalars {
result += String(c.value, radix: radix) + " "
}
return result
}
println(textToBinary("a"))
println(textToBinary("abc", radix: 10))
println(textToBinary("€20", radix: 16))
println(textToBinary("😄"))
(The last one is a smiley face but somehow my browser can't display it).
Edit: if you want to pad your strings to 8-character long, try this:
let str = "00000000" + String(c.value, radix: radix)
result += str.substringFromIndex(advance(str.startIndex, str.characters.count - 8)) + " "
The first line adds eight 0 the left of your string. The second line takes the last 8 characters from the padded string.

Format Numbers in Textfields using Swift

I am trying to format a number from a UITextfield, as its being typed, to a decimal with commas.
I have done so with the following code:
#IBAction func editingDidBegin(sender : AnyObject)
{
costField.addTarget(self, action: Selector("textFieldDidChange:"), forControlEvents: UIControlEvents.EditingChanged)
}
func textFieldDidChange(theTextField:UITextField) -> Void
{
var textFieldText = theTextField.text.stringByReplacingOccurrencesOfString(",", withString: " ", options: NSStringCompareOptions.RegularExpressionSearch, range: Range(start: theTextField.text.startIndex, end: theTextField.text.endIndex))
var formatter:NSNumberFormatter = NSNumberFormatter()
formatter.numberStyle = NSNumberFormatterStyle.DecimalStyle
var formattedOutput = formatter.stringFromNumber(textFieldText.bridgeToObjectiveC().integerValue)
costField.text = formattedOutput
}
The problem with this, is after four digits are entered, everything after the comma is deleted. For example if I enter 4000 it formats to 4,000, then if I type another number like 8 it reformats to 48.
Is there another way I can format this, maybe through IB or how can I fix the code?
Replace the line with:
var textFieldText = theTextField.text.stringByReplacingOccurrencesOfString(",", withString: "", options: NSStringCompareOptions.RegularExpressionSearch, range: Range(start: theTextField.text.startIndex, end: theTextField.text.endIndex))
(I only removed the space between the double quotes).
Fact is, NSNumberFormatter doesn't like the added spaces in the string.
Works fine afterwards.
I know I am late to the party but this worked well for me.
var phoneNumber = " 1 (888) 555-5551 "
var strippedPhoneNumber = "".join(phoneNumber.componentsSeparatedByCharactersInSet(NSCharacterSet.decimalDigitCharacterSet().invertedSet))
It takes out the spaces and strips out the non decimal numeric characters.
The end result is "1888555551"
I've updated this answer to the newest version of swift. This borrows 90% from the two answers above however, also accounts for nil exception from the textfield when the textfield is cleared.
func textFieldDidChangeCommas(theTextField:UITextField) -> Void
{
if theTextField.text != nil {
var textFieldText = theTextField.text!.stringByReplacingOccurrencesOfString(",", withString: "", options: NSStringCompareOptions.RegularExpressionSearch, range: Range(start: theTextField.text!.startIndex, end: theTextField.text!.endIndex))
var formatter:NSNumberFormatter = NSNumberFormatter()
formatter.numberStyle = NSNumberFormatterStyle.DecimalStyle
if textFieldText != "" {
var formattedOutput = formatter.stringFromNumber(Int(textFieldText)!)
costField.text = formattedOutput
}
}
}

Resources