Bronze Challenge (localized)

#1
//
//  ConversionViewController.swift
//  WorldTrotter
//
//  Created by Arthur de Boer on 15-03-16.
//  Copyright © 2016 Arthur de Boer. All rights reserved.
//

import UIKit


let number                      =   NSNumberFormatter()
let locale                      =   NSLocale.currentLocale()
let decimalCode                 =   locale.objectForKey(NSLocaleDecimalSeparator) as! NSString


class ConversionViewController: UIViewController, UITextFieldDelegate {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        number.decimalSeparator = decimalCode as String
    }
    
    
    @IBOutlet var celsiusLabel: UILabel!
    
    var fahrenheitValue: Double? {
        didSet {
            updateCelsiusLabel()
        }
    }
    
    var celsiusValue: Double? {
        if let value = fahrenheitValue {
            return (value - 32) * (5/9)
        } else {
            return nil
        }
    }
    
    
    func updateCelsiusLabel() {
        if let value = celsiusValue {
            celsiusLabel.text = numberFormatter.stringFromNumber(value)
        } else {
            celsiusLabel.text = "???"
        }
    }
    
    @IBOutlet var textField: UITextField!
    
    @IBAction func fahrenhetFieldEditingChanged(textField: UITextField) {
        if let text = number.numberFromString(textField.text!) {
            fahrenheitValue = Double(text)
        } else {
            fahrenheitValue = nil
        }
       
    }
    
    @IBAction func dismissKeyboard(sender: AnyObject) {
        textField.resignFirstResponder()
    }
    
    let numberFormatter: NSNumberFormatter = {
        let nf = NSNumberFormatter()
        nf.numberStyle = .DecimalStyle
        nf.minimumFractionDigits = 0
        nf.maximumFractionDigits = 1
        return nf
    }()
    
    
    
    func textField(textField: UITextField,shouldChangeCharactersInRange range: NSRange,replacementString string: String) -> Bool {
        let existingTextHasDecimalSeparator = textField.text?.rangeOfString(number.decimalSeparator)
        let replacementTextHasDecimalSeperator = string.rangeOfString(number.decimalSeparator)
            
        if existingTextHasDecimalSeparator != nil && replacementTextHasDecimalSeperator != nil {
            return false
        }
        
        let replacementText = NSCharacterSet(charactersInString: string)
        let allowedCharacters = NSCharacterSet.init(charactersInString: "0123456789" + number.decimalSeparator)
        if !allowedCharacters.isSupersetOfSet(replacementText) {
            return false
        }

        return true
    }
    
}
#2

Love the localized solution to the problem. I had a similar solution (non-localized), but realized that the user couldn’t paste in a number if both the original text and the replacement text contain a decimal. In the event that you are pasting over the original decimal, the result would be a legit number. Instead I went with this approach to evaluate the combined string as a double:

func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {

        let str = (textField.text! as NSString).stringByReplacingCharactersInRange(range, withString: string) // String class doesnt support NSRange operations.  Required to downconvert to NSString
        
        let num = Double(str)
        if num == nil && string != "" {
            return false
        } else {
            return true
        }
    }
#3

Great solutions and thank you for enlightening me about String vs. NSString methods, NSLocale and much more. The locale solution allowed >1 decimal separators as I tested it. If we retain the original check for decimal duplicates, a la “existingTextHasDecimalSepartor”, maybe we avoid the duplicate decimal bug? This revision allows multiple leading zeroes (e.g. “00001”). While 0001 is still a valid number, and not part of the Bronze Challenge, it needs more work.

func textField(textField: UITextField,
            shouldChangeCharactersInRange range: NSRange,
            replacementString string: String) -> Bool {
        // Ch. 4 Bronze Challenge solution provided by AWdeBoer.
        let replacementText = NSCharacterSet(charactersInString: string)
        let allowedCharacters = NSCharacterSet.init(charactersInString: "0123456789" + number.decimalSeparator)

        let existingTextHasDecimalSeparator = textField.text?.rangeOfString(".")
        let replacementTextHasDecimalSeparator = string.rangeOfString(".")
        // Prevent >1 decimal in the input text and reject disallowed characters.
        if !allowedCharacters.isSupersetOfSet(replacementText) || (existingTextHasDecimalSeparator != nil 
            && replacementTextHasDecimalSeparator != nil) { 
            return false
        }
            return true
    }
#4

I did some tests and it looks like the locale, decimalCode and viewDidLoad() declarations are unnecessary because the NSNumberFormatter is by default already initialized with the decimalSeparator of the OS locale.