読者です 読者をやめる 読者になる 読者になる

SIN@SAPPOROWORKSの覚書

C#を中心に、夜な夜な試行錯誤したコードの記録です。

SimpleNumpadを利用した、拡張コントロールの作成(Swift)

SimpleNumpad

UITextField では、コントロールにフォーカスが入った時にキーボードが自動的に出現させることができます。今回は、このように使用できる数値入力コントロールを作成してみたいと思います。

入力の際に出現させるキーボードは、札幌のnotoroidさんが制作されたSimpleNumpad を使用させて頂きました。

なお、SimpleNumpadの詳しい利用方法については、
[iOS][Objective-C] 数値入力をベンリでクールにしてくれる SimpleNumpad | Developers.IO
で紹介されています。

プロジェクトの作成とSimpleNumpadの導入

SimpleNumpadは、CocoaPodsでプロジェクトに組み込むことができます。

CocoaPodsを利用した、プロジェクト作成の手順は、下記のとおりです。
(1) Single View Application でプロジェクトを新規作成(プロジェクト名を、SampleSinpleNumpadとした)
(2) プロジェクトフォルダに移動して、下記の内容でPodfileを作成

pod 'SimpleNumpad', :git => 'https://github.com/notoroid/SimpleNumpad.git'

(3) $pod install を実行
(4) 生成されたプロジェクト名.xcworkspace を開く
(5) プロジェクトフォルダ内で、下記の内容でBridgingHeader.hを作成

#ifndef SampleSimpleNumpad_BridgingHeader_h
#define SampleSimpleNumpad_BridgingHeader_h

#import <SimpleNumpad/SimpleNumpad.h>

#endif

(6) Build Settingの Swift Compiller - Code Generation で Objective-C Bridging Header にファイル名を追加する
f:id:furuya02:20150819020346p:plain

拡張コントロール(UINumField)の作成

今回の拡張コントロールは、UILabelを継承して作成しました。
コントロールは、storyboardからも使用できるように@IBDesignable属性を設定し、SimpleNumpadのデリゲートクラスとなれるようIDPNumpadViewControllerDelegateも追加しています。

import UIKit

@IBDesignable
class UINumField: UILabel , IDPNumpadViewControllerDelegate{
   private var vc:IDPNumpadViewController?
}

初期化処理

まずは、initialize()を定義して、コントロールの見た目と、タッチイベントを処理できるようにしました(1)。
この初期化処理は、実行時に init(coder aDecoder: NSCoder) から呼び出すようにしたのですが(2)、この init(NSCoder) を定義する場合は、
同時に init(CGRect) も定義(3)しないと、storyboadrでエラーが発生してしまいます。
f:id:furuya02:20150819021849p:plain

なお、この見た目の初期化をstoryboard上でリアルタイムに表示したい場合は、prepareForInterfaceBuilder()から行うことできます(4)。

    func initialize(){ // <= (1)
        // UILabelを Storyboard で置いた時のデフォルト値を削除する
        text="0.0"
        // Labelのフォントのサイズを修正
        font = UIFont.systemFontOfSize(35)
        // Labelがタッチイベントを受け取るように設定する
        userInteractionEnabled = true
    }

    required init(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        
        initialize() //  実行時は、ここを通る <= (2)

        // SimpleNumpadの生成が、Storyboardで処理できないため、ここで初期化する
        vc = IDPNumpadViewController(style:IDPNumpadViewControllerStyle.CalcApp,inputStyle: IDPNumpadViewControllerInputStyle.Number,showNumberDisplay: false)
        vc?.delegate = self // SimpleNumpadのデリゲートクラスを自身とする
        
    }
    // init() イニシャライザを書く場合は、これも同時に定義しないとエラーとなる
    // Failed to update auto layout status interface Builder Cocoa touch
    override init(frame: CGRect) {  // <= (3)
        super.init(frame: frame)
    }
    
    // ここでStoryboard上の見た目を初期化する
    override func prepareForInterfaceBuilder() { // <= (4)
        initialize()
    }


プロパティ

続いて、コントロールにフォーカスがある時と無い時に、背景色を変化させるようにし、この色を変更できるようにしました。
@IBInspectable属性を指定することで、storyboardから、プロパティ値の変更が可能になります。

// フォーカスがない時の背景色
    @IBInspectable var offColor: UIColor = .lightGrayColor(){
        didSet{ //storyboardでリアルタイムに表示するため
            backgroundColor = offColor
        }
    }
    // フォーカスがある時の背景色
    @IBInspectable var onColor: UIColor = .clearColor() {
        didSet{ //storyboardでリアルタイムに表示するため
            backgroundColor = onColor
        }
    }

フォーカス時の処理

コントロールがフォーカスを受け取った際の処理は、次のようになります。
ステータスと背景色を変更し、SimpleNumpadをモーダルビューとして表示しています。
なお、このモーダルビューは、この拡張コントロールが置かれている親のViewからポップアップする必要があるので、親のレスポンダーを検索するparentViewControllerを定義しました(6)。

// コントロールがタッチされた際のイベント処理
    override func touchesEnded(touches: Set<NSObject>, withEvent event: UIEvent) {
        if isBusy {
            return // 既に入力中の場合は処理なし
        }
        // ステータスを「入力中」に変更
        isBusy = true
        // 背景色を変更
        backgroundColor = onColor
        // 親のビューコントローラのモーダルビューとして、vc(SimpleNumpad)を開く
        parentViewController!.presentViewController(self.vc!, animated: true, completion: {})
    }
    
    // 親となるUIViewControllerを検索(取得)する
    var parentViewController: UIViewController? { // <= (6)
        var parentResponder: UIResponder? = self
        while parentResponder != nil {
            parentResponder = parentResponder!.nextResponder()
            if let viewController = parentResponder as? UIViewController {
                return viewController
            }
        }
        return nil
    }

キー入力時の処理

キー入力時の処理は、デリゲートで委譲されたメソッドで処理します。
Enterが押された場合は、モーダルビューを閉じると同時に、背景色を戻して、フォーカスが外れたことを表現しています(7)。

// SimpleNumpadの内部の値が変化した際のイベント処理
    func numpadViewControllerDidUpdate(numpadViewController: IDPNumpadViewController!) {
        self.text = "\(vc!.value)"
    }
    // Enterが押された時のイベント処理
    func numpadViewControllerDidEnter(numpadViewController: IDPNumpadViewController!) { // <= (7)
        // モーダルビューを閉じる
        parentViewController!.dismissViewControllerAnimated(true, completion: {})
        // ステータスを変更
        isBusy = false
       // 背景色を変更
        backgroundColor = offColor
    }

コントロールの使用

それでは、作成した拡張コントロールを使用してみます。
UILabelをStoryboardに配置します。
f:id:furuya02:20150819020450p:plain
続いて、Custom Class で UINumField を選択します
f:id:furuya02:20150819020459p:plain:w500
あとは、@IBInspectable属性を指定されたプロパティを変更します。
f:id:furuya02:20150819025332p:plain:w500

利用に際しての作業は、以上です。

実行している様子は、次のとおりです。
f:id:furuya02:20150819025549g:plain

しかし、SimpleNumpad いいっすね〜

コードは、下記に置きました。github.com

まとめ

今回は、SimpleNumpadを利用させて頂いて、拡張コントロールを作成してみました。
コントロールに関するdelegate処理などを、全部コントロールクラスに詰め込むことで、利用は非常にシンプルになると思います。

参考資料

[iOS][Objective-C] 数値入力をベンリでクールにしてくれる SimpleNumpad | Developers.IO
notoroid/SimpleNumpad · GitHub