0

I'm trying to make a feature in my app that when a user types in a text field, the text converts into a unicode family.

Like below, there is an image of a user typing. And as the user types, you can see different unicode family characters that when a user types on a cell, you can copy the text and paste it somewhere else.

enter image description here

If I would like to turn my text into the black bubble unicode family like the screenshot above, how can I do that?

Erik Batista
  • 227
  • 2
  • 15

1 Answers1

3

You can define a character map. Here's one to get you started.

let circledMap: [Character : Character] = ["A": "", "B": "", "C": "", "D": ""] // The rest are left as an exercise 
let circledRes = String("abacab".uppercased().map { circledMap[$0] ?? $0 })
print(circledRes)

If your map contains mappings for both upper and lowercase letters then don't call uppercased.

Create whatever maps you want. Spend lots of time with the "Emoji & Symbols" viewer found on the Edit menu of every macOS program.

let invertedMap: [Character : Character] = ["a": "ɐ", "b": "q", "c": "ɔ", "d": "p", "e": "ǝ", "f": "ɟ", "g": "ƃ", "h": "ɥ"]

In a case like the circled letters, it would be nice to define a range where you can transform "A"..."Z" to ""..."".

That actually takes more code than I expected but the following does work:

extension String {
    // A few sample ranges to get started
    // NOTE: Make sure each mapping pair has the same number of characters or bad things may happen
    static let circledLetters: [ClosedRange<UnicodeScalar> : ClosedRange<UnicodeScalar>] = ["A"..."Z" : ""..."", "a"..."z" : ""...""]
    static let boxedLetters: [ClosedRange<UnicodeScalar> : ClosedRange<UnicodeScalar>] = ["A"..."Z" : ""..."", "a"..."z" : ""...""]
    static let italicLetters: [ClosedRange<UnicodeScalar> : ClosedRange<UnicodeScalar>] = ["A"..."Z" : ""..."", "a"..."z" : ""...""]

    func transformed(using mapping: [ClosedRange<UnicodeScalar> : ClosedRange<UnicodeScalar>]) -> String {
        let chars: [UnicodeScalar] = self.unicodeScalars.map { ch in
            for transform in mapping {
                // If the character is found in the range, convert it
                if let offset = transform.key.firstIndex(of: ch) {
                    // Convert the offset from key range into an Int
                    let dist = transform.key.distance(from: transform.key.startIndex, to: offset)
                    // Build new index into value range
                    let newIndex = transform.value.index(transform.value.startIndex, offsetBy: dist)
                    // Get the mapped character
                    let newch = transform.value[newIndex]
                    return newch
                }
            }

            // Not found in any of the mappings so return the original as-is
            return ch
        }

        // Convert the final [UnicodeScalar] into a new String
        var res = ""
        res.unicodeScalars.append(contentsOf: chars)

        return res
    }
}

print("This works".transformed(using: String.circledLetters)) //  

The above String extension also requires the following extension (thanks to this answer):

extension UnicodeScalar: Strideable {
    public func distance(to other: UnicodeScalar) -> Int {
        return Int(other.value) - Int(self.value)
    }

    public func advanced(by n: Int) -> UnicodeScalar {
        let advancedValue = n + Int(self.value)
        guard let advancedScalar = UnicodeScalar(advancedValue) else {
            fatalError("\(String(advancedValue, radix: 16)) does not represent a valid unicode scalar value.")
        }
        return advancedScalar
    }
}
rmaddy
  • 314,917
  • 42
  • 532
  • 579
  • Hey @rmaddy I implemented this and got to finish my app because you gave me this solution. Thank you for the solution! Much appreciated. – Erik Batista Aug 01 '19 at 18:30