[Swift] クロージャを使ってUIViewのイベントを外部で指定できるようにする

written: kengo
tag:

Swiftではクロージャを関数ポインタに近い形で使えるため、UIViewのタッチイベントなどを簡単に外部に分離できます。 今回は私のお気に入りの方法で、タッチイベント4種類を外部から呼べるようにする例を挙げます。 (※2015-10-20: Swift1.2からSwift2.0に修正しました)

UIView派生クラス

タッチイベント用にev_touches_****という変数を4つ作成し、それぞれの初期化にクロージャを入れておきます。 クロージャ内の処理はからっぽにしておきます。こうするとクロージャを初期化で入れておけるので、各オブジェクトのnilチェックしなくても問題なく動作します。またSwiftの型推論が力を発揮します。関数ポインタのようにややこしい型定義をしなくてすむので楽チンです。

KNGView.swift

import UIKit

class KNGView : UIView
{
    // 関数を格納するイベント用のクロージャオブジェクト
    var ev_touches_began     = { (view:KNGView, touches:Set<UITouch>, event: UIEvent)->Void in }
    var ev_touches_moved     = { (view:KNGView, touches:Set<UITouch>, event: UIEvent)->Void in }
    var ev_touches_ended     = { (view:KNGView, touches:Set<UITouch>, event: UIEvent)->Void in }
    var ev_touches_cancelled = { (view:KNGView, touches:Set<UITouch>?, event: UIEvent)->Void in }
    
    override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent)
    {
        super.touchesBegan(touches, withEvent:event)
        ev_touches_began(self, touches, event)	// クロージャをコール
    }
    
    override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent)
    {
        super.touchesMoved(touches, withEvent:event)
        ev_touches_moved(self, touches, event)	// クロージャをコール
    }

    override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent)
    {
        super.touchesEnded(touches, withEvent:event) 
        ev_touches_ended(self, touches, event)	// クロージャをコール
    }

    override func touchesCancelled(touches: Set<UITouch>?, withEvent event: UIEvent)
    {
        super.touchesCancelled(touches, withEvent:event)
       	ev_touches_cancelled(self, touches, event)	// クロージャをコール
    }

クロージャを入れた変数をtouchesBeganなど各タッチイベント関数内でコールします。 この段階ではevtouches****の各変数の処理は空っぽなので何も起きません。

派生クラスのイベントを外部で呼び出し

次に、上記で作った派生クラスを実体化します。ViewControllerのviewDidLoad関数などで行ってください。

MyViewController.swift


class MyViewController : UIViewController
{
	var view:KNGView!

    override func viewDidLoad()
    {
        // 親のviewDidLoadを呼ぶ[必須]
        super.viewDidLoad()
        
        self.view.backgroundColor = UIColor.whiteColor()
        
		self.view = KNGView()
        self.view.frame = CGRect(x: 180, y:180, width: 100, height: 100)
        self.view.addSubview(self.view)
            
        // ev_touches_beganにクロージャを代入。タッチしたときの処理を外部から差し替えられる
        self.view.ev_touches_began = { (view:KNGView, touches:Set<UITouch>, event: UIEvent) in
           println("クロージャで処理を差し替え")
        }
		
		// ev_touches_movedに関数を代入。これも可能
		self.view.ev_touches_moved = self.moved
    }
	
	func moved(view:KNGView, touches:Set<UITouch>, event: UIEvent)
	{
		println("関数代入で処理を差し替え")
	}
            
}

boost C++のfunction/bindに近い感じでしょうか。かつ、普通の関数も、クラスメンバ関数も、クロージャも、違和感なく代入できるのは嬉しいところです。Objective-Cではselectorで似た形に実現できましたが通常の関数とBlocksを同時に扱うとなるとややこしくなりがちでした。Swiftではまとめて扱える上、さらにシンプルに書けますので積極的に使っていきたいなあと思います。