How to initialize a UIKeyboardType with a string?

353 Views Asked by At

I have a jsondata struct that is build from a JSON file that contains this :

{
  inputFieldTitle: "Total Cost",
  keyboardType: "numberPad"
}

In my code, how can I use the attribute keyboardType to initialize the UIKeyboardType enum in order to set the keyboard type on my TextField?

Normally we would do this to get a numberPad keyboard (SwiftUI) :

TextField(jsondata.inputFieldTitle) { ... }
  .keyboardType(.numberPad)

But in my scenario, I can't hardcode the keyboardType with .numberPad, I have to use what's specified in the jsondata, how can I use that value to set the keyboardType on the TextField? This obvisouly does not work because the UIKeyboardType is of type Int :

TextField(jsondata.inputFieldTitle) { ... }
  .keyboardType(UIKeyboardType(rawValue: jsondata.keyboardType))

Any tips? Thanks.

2

There are 2 best solutions below

1
On

UIKeyboardType is-a Int, so you cannot use rawValue, because your json value is string.

Here is possible solution

extension UIKeyboardType {
    static private let types = ["numberPad": numberPad]   // << extend to all supported
    init(_ value: String) {
        if let result = Self.types[value] {
            self = result
        } else {
            self = .default
        }
    }
}

and you can now use it as

TextField(jsondata.inputFieldTitle) { ... }
  .keyboardType(UIKeyboardType(jsondata.keyboardType))
//  .keyboardType(UIKeyboardType(jsondata.keyboardType ?? "")) // if optional
3
On

You can conform UIKeyboardType to Decodable and implement your own custom decoder method:

extension UIKeyboardType: Decodable {
    public init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        do {
            let rawValue = try container.decode(Int.self)
            guard let keyboardType = UIKeyboardType(rawValue: rawValue) else {
                throw DecodingError.dataCorruptedError(in: container, debugDescription: "invalid UIKeyboardType rawValue: \(rawValue)")
            }
            self = keyboardType
        } catch DecodingError.typeMismatch {
            let string = try container.decode(String.self)
            guard let keyboardType = UIKeyboardType(string) else {
                throw DecodingError.dataCorruptedError(in: container, debugDescription: "invalid UIKeyboardType string: \(string)")
            }
            self = keyboardType
        }
    }
    init?(_ string: String) {
        switch string {
        case "default":                 self = .default
        case "asciiCapable":            self = .asciiCapable
        case "numbersAndPunctuation":   self = .numbersAndPunctuation
        case "URL":                     self = .URL
        case "numberPad":               self = .numberPad
        case "phonePad":                self = .phonePad
        case "namePhonePad":            self = .namePhonePad
        case "emailAddress":            self = .emailAddress
        case "decimalPad":              self = .decimalPad
        case "twitter":                 self = .twitter
        case "webSearch":               self = .webSearch
        case "asciiCapableNumberPad":   self = .asciiCapableNumberPad
        case "alphabet":                self = .alphabet
        default: return nil
        }
    }
}

extension UIKeyboardType: Encodable {
    public func encode(to encoder: Encoder) throws {
        var encoder = encoder.singleValueContainer()
        let string: String
        switch self {
        case .default:                 string = "default"
        case .asciiCapable:            string = "asciiCapable"
        case .numbersAndPunctuation:   string = "numbersAndPunctuation"
        case .URL:                     string = "URL"
        case .numberPad:               string = "numberPad"
        case .phonePad:                string = "phonePad"
        case .namePhonePad:            string = "namePhonePad"
        case .emailAddress:            string = "emailAddress"
        case .decimalPad:              string = "decimalPad"
        case .twitter:                 string = "twitter"
        case .webSearch:               string = "webSearch"
        case .asciiCapableNumberPad:   string = "asciiCapableNumberPad"
        }
        try encoder.encode(string)
    }
}

extension DataProtocol {
    var string: String? { String(bytes: self, encoding: .utf8) }
}


Playground testing:

struct Test: Codable {
    let inputFieldTitle: String
    let keyboardType: UIKeyboardType
}

let numberPadJSON = """
{
  "inputFieldTitle": "Total Cost",
  "keyboardType": "numberPad"
}
"""

do {
    let test = try JSONDecoder().decode(Test.self, from: Data(numberPadJSON.utf8))
    print(test.keyboardType.rawValue)  // 4
    let encoded = try JSONEncoder().encode(test)
    print(encoded.string ?? "")  // {"inputFieldTitle":"Total Cost","keyboardType":"numberPad"}
} catch {
    print(error)
}