5
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

UIButton の動作を Callback で設定する方法

Posted at

Cocoa / UIKit Framework で Swift 開発する時、わりとよくある不満?の一つは、Cocoa は当時動的な言語 Objective-C のために作ったフレークワークのため、動的制御の思想があっちこっちで見られるが、Swift は静的な言語なので Cocoa フレームワークを使う時時々気持ち悪いコードも見られます(少なくとも筆者はかなり気持ち悪いと思ってます…)。

例えば UIButton がまさにその代表格です。ボタン動作は全てセレクターで定義され、例えば一つボタンを追加したいだけの場合でも、ボタン動作を別メソッド(例えば func buttonTapped(sender: UIButton) とか)で定義しなければなりません。一連のボタン群ならこの手法では動作が全てまとめられて switch 文で tag で動作分岐させればコードとしてスッキリすることもありますが、逆に非常に単純な動作を行って欲しいっていうことの時、わざわざ別メソッドを作るのも面倒ですし、そもそも論として静的言語の Swift に Runtime に動作が決まる Selector でボタンを作るのは非常に気持ち悪い以下略。なのでまあ UIButton に動作をあらかじめ埋め込んでおく Callback 方式を対応して欲しいとずっと昔から思ってましたが未だに公式がそれをやってくれてないので、自分でなんとかするしかないのです。

というわけで早速 UIButton のサブクラスを作ります:

import UIKit

class CallbackButton: UIButton {
	
	private var action: (() -> Void)?
	
	init(frame: CGRect, action: (() -> Void)? = nil) {
		
		self.action = action
		
		super.init(frame: frame)
		
		self.addTarget(self, action: "tapped:", forControlEvents: .TouchUpInside)
		
	}

	required init?(coder aDecoder: NSCoder) {
	    fatalError("init(coder:) has not been implemented")
	}
	
	func setAction(action: () -> Void) {
		self.action = action
	}
	
	func tapped(sender: CallbackButton) {
		self.action?()
	}
	
}

まあこれで非常にわかりやすいと思いますが、UIButton と同じように CGRect から形を作り、その時にすぐにでも action を設定できますが、もし諸事情によりそれができない場合(親クラスがまだ init されてない時など)は後からでも setAction で定義できます。そしてまあここは仕方ないのですが一応タップ時のメソッドを定義し、その時に action?() を実行するようにします。

それではこれを使うとどれくらいスッキリするかというと、例えば適当にとある UIView クラスにボタンを追加するとしましょう。通常の UIButton を使う場合は下記のようになります:

class SomeView: UIView {
	
	var a = 1
	
	override init(frame: CGRect) {
		
		super.init(frame: frame)
		
		let button = UIButton(frame: CGRectZero)
		button.addTarget(self, action: "tapped:", forControlEvents: .TouchUpInside)
		self.addSubview(button)
		
	}

	required init?(coder aDecoder: NSCoder) {
	    fatalError("init(coder:) has not been implemented")
	}
	
	func tapped(sender: UIButton) {
		self.a *= 2
	}
	
}

そして Callback 方式を使うとこのようになります:

class SomeView: UIView {
	
	var a = 1
	
	override init(frame: CGRect) {
		
		super.init(frame: frame)
		
		let button = CallbackButton(frame: CGRectZero) { () -> Void in
			self.a *= 2
		}
		self.addSubview(button)
		
	}

	required init?(coder aDecoder: NSCoder) {
	    fatalError("init(coder:) has not been implemented")
	}
	
}

とこんな風に、余分な tapped: メソッドを使わなくて済むのです。

まあ何より、Selector を使わずに済むってのが一番大きいじゃないかと思うんですね、Swift の場合 Selector の補完とかないし、結構「:」を必要な時に入れ忘れたり逆に要らない時に入れちゃったりすることによる Runtime Error 多いんですよな…

5
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?