[Swift3] 整数入力用UITextFieldを自作してみた


イメージ

仕様

  • 整数値専用のUITextField
  • 文字は入力不可 ※keyboardTypeにnumberPadを指定するが、コピペ対策
  • 編集終了時にカンマ編集を行い、フォーカスインでカンマを削除する
  • カンマ編集する/しないはIB上でプロパティ指定可能

環境

Items Version
Xcode 8.2
Swift 3.0.2

コード

定義

NumberTextField.swift
import UIKit

@IBDesignable

/// 数値専用UITextField
class NumberTextField: UITextField {

    // MARK: - 変数

    /// カンマ編集有無
    private var isCommaFormat = true

    /// カンマ編集有無
    @IBInspectable var CommaFormat: Bool = true {
        didSet {
            self.isCommaFormat = CommaFormat
        }
    }

    // MARK: - Controll Event

    /// ロードされた後に呼び出される
    override func awakeFromNib() {
        super.awakeFromNib()
        self.keyboardType = .numberPad
        self.textAlignment = .right
    }

    // MARK: - Method

    /// 入力が開始された際に呼ばれる
    func beginEditing() {
        guard let text = self.text else {
            return
        }
        self.text = text.replacingOccurrences(of: ",", with: "")
    }

    /// 入力値が変更された場合に呼ばれる
    func shouldChange(range: NSRange, string: String) -> Bool {
        // 0-9以外の数字以外不可
        return string.isEmpty || string.range(of: "^[0-9]+$", options: .regularExpression) != nil
    }

    /// 入力値が確定された場合に呼ばれる
    func endEditing() {
        guard let text = self.text else {
            return
        }
        if self.isCommaFormat {
            self.text = Int(text)?.withComma
        }
    }

}
Extentions.swift
import Foundation

/// Int拡張
extension Int {
    /// カンマ付け
    var withComma: String {
        let decimalFormatter = DecimalFormatter()
        guard let s = decimalFormatter.string(from: self as NSNumber) else {
            fatalError()
        }
        return s
    }
}

/// String拡張
extension String {
    /// カンマ区切り数字のカンマどり
    var noComma: Int {
        if self.characters.count == 0 {
            return 0
        }
        let decimalFormatter = DecimalFormatter()
        guard let i = decimalFormatter.number(from: self) else {
            preconditionFailure("NumberFormatter.number method failure!")
        }
        return Int(i)
    }
}

/// カンマ編集制御用NumberFormatter拡張クラス
class DecimalFormatter: NumberFormatter {
    override init() {
        super.init()
        self.locale = Locale(identifier: "ja_JP")
        self.numberStyle = .decimal
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }
}

使用例

ViewController.swift
import UIKit

class ViewController: UIViewController, UITextFieldDelegate {

    @IBOutlet weak var numberTextField: NumberTextField!
    @IBOutlet weak var textField: UITextField!

    override func viewDidLoad() {
        super.viewDidLoad()

        self.numberTextField.delegate = self
        self.textField.delegate = self
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }

    // MARK: - UITextField Delegate

    /// UITextFieldの編集開始
    func textFieldDidBeginEditing(_ textField: UITextField) {
        if let numberTextField = textField as? NumberTextField {
            numberTextField.beginEditing()
        }
    }

    /// UITextFieldの値変更
    func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
        if let numberTextField = textField as? NumberTextField {
            return numberTextField.shouldChange(range: range, string: string)
        }
        return true
    }

    /// UITextFieldの編集終了
    func textFieldDidEndEditing(_ textField: UITextField) {
        if let numberTextField = textField as? NumberTextField {
            numberTextField.endEditing()
        }
    }

}

課題

ViewControllerに処理をdelegateしておきながら、NumberTextField自身のメソッドを呼ばせるのが何か気持ち悪い。
エレガントな解決方法があったらコメントをお願いします!