今日はaddTargetメソッドの使い方と難しい引数(selector, sender)について自分のアプリを作りつつまとめます。
ボタンをタップしたら画像を切り替えるためにaddTargetを使う
画像は2枚あります。
ボタンを押せば画像1→画像2と切り替わる「だけ」のアプリを作りつつaddTargetメソッドの使い方や引数について解説していきます。
addTargetメソッドとは
addTargetメソッドはUIControlクラスが持ってるメソッドで、UIButtonなどUIControlクラスを継承しているクラスで使えます。
addTargetメソッドを使うことで、イベントを検知してそのタイミングで処理をさせることを可能とします。
今回はUIButtonをタップしたら画像1→画像2と切り替える処理をします。
addTargetを使う前準備
●プロジェクト新規作成
addTargetTestというプロジェクトを作成します。
storyboardを表示して、スマホを横向きにしておきます。
次にソースコードを表示します。
用意しておいた画像をプロジェクトにインポートします。
画面上部のFileからAdd Files toで「Add」します。
普段はInterface BuilderにButtonやUIImageViewをマウスで配置してAction接続でやるところですが、今回はすべてソースコード上でやっていきます。
犬の画像を縮小して縦横比を維持して画面中央に表示させる方法
犬の画像がスマホの画面より大きいので、縮小して画面中央に表示します。
ソースコードは以下のように記述。
override func viewDidLoad() { super.viewDidLoad() let img = UIImage(named: "dog1.png") let imgView = UIImageView() imgView.image = img //スクリーンサイズ let screenWidth = self.view.frame.width let screenHeight = self.view.frame.height //画像サイズを取得 let imgW = img!.size.width let imgH = img!.size.height print("screen w, h : \(screenWidth), \(screenHeight) img w, h : \(imgW), \(imgH)") //スマホ高さが414で画像が600だと画像を414/600=0.69倍すればよい.スマホ横向きなので高さに合わせる let hScale = screenHeight / imgH //imgViewをとりあえず(0,0)座標から描画し、縦横は倍率で計算してセット、縦横比も保たれる imgView.frame = CGRect(x:0, y:0, width: imgW * hScale, height: imgH * hScale) //imgViewの中央を画面中央とする imgView.center = self.view.center //imgViewを乗せます self.view.addSubview(imgView) }
4行目でUIImageでdog1.pngを生成しています。
6行目でUIImageViewのimageプロトコルに画像をセット。
9、10行目でスマホの画面サイズを取得しておきます。
13、14行目でロードした犬画像の縦横サイズを取得しています。
19行目で画像に対する倍率を計算しておいて。
22行目で画像サイズに倍率を掛け算してimgViewの縦横サイズを設定しています。いったん描画はx=0, y=0で行います。
25行目で画像の中央と画面中央をあわせて、28行目でaddSubviewでimgViewを乗せて表示させます。
ボタンを画像の上に置きます
今の所、犬画像があるだけ。なのでそのうえにボタンを置いておきます。
//普通のボタンを生成 let btn = UIButton(type: UIButtonType.system) //画面いっぱいの大きさにする btn.frame = CGRect(x:0, y:0, width:screenWidth, height:screenHeight) btn.layer.borderColor = UIColor.cyan.cgColor //ボタンに色をつけて見やすくしておく btn.layer.borderWidth = 2 btn.setTitle("ボタン", for: UIControlState.normal) //imgView.addSubview(btn)これだと画像よりボタンが小さくなっちゃうのでだめ self.view.addSubview(btn)
今回はUIButtonのインスタンスを生成して、スマホサイズと同じ大きさにします。
ボタンが透明で本当に配置されているのか見えないのでボタンの枠を青色にして、テキストに「ボタン」と表示しています。
ボタンは画像(imageView)の上に置くと画像サイズより小さく配置されてしまうので駄目だということがわかりました。
なのでself.view.addSubviewでボタンを配置しています。これで以下のようにシミュレーターが表示されます。
次からいよいよaddTargetメソッドの使い方です。
ついでにボタンに一時的につけた青枠線とテキストを消しておく。
addTargetの使い方、引数selector、senderのおさらい
addTargetメソッドを記述しようとすると補完機能が働き、入力項目が出てくれる。これは助かる。
addTargetメソッドの記法は以下のとおりです。
UIButton.addTarget(_ target: Any?, action: Selector, for controlEvents: UIControlEvents)
呼ぶ側で以下のように記述。
btn.addTarget(self, action: #selector(self.changeImg(_:)), for: UIControlEvents.touchUpInside)
引数については後でまとめるけど、1個目のselftはメソッドがどこにあるかを指定するのでself(=ViewController)を指定。
第2引数にはselectorで実行するメソッド(changeImg)を記述。引数は(_:)として呼び出し元のbtnを渡すようにしています。
第3引数はどんなイベントが発生したかをUIControlEventsの定数で指定。
今回はボタンがタップされたらchangeImgというメソッドが動くようにしたい。changeImgメソッドの中で画像を変更(dog1→dog2へ)させる処理を記述します。
しかしこのメソッドには画像を設定したUIImageViewが無いのでこのメソッド内で使えるようにしないといけない。
私がやろうとしたのは以下のメソッドに第二引数をつけてUIImageViewを受け取ろうとしました。
@objc func changeImg(_ sender: UIButton) { }
しかしメソッドに第二引数をつけようとしたのだができない。。
このchangeImgメソッドにUIImageViewのインスタンスを渡したい。けど以下のようにするとエラーが出てしまいビルドができない状況に。
btn.addTarget(self, action: #selector(self.changeImg(_:, tmpImgView: imgView)), for: UIControlEvents.touchUpInside) @objc func changeImg(_ sender: UIButton, tmpImgView: UIImageView) { }
エラー内容
Expected expression in list of expressions
いろいろ調べてみたけどセレクターのメソッドに引数を追加することができないことが判明。
【参考】
https://teratail.com/questions/53889
https://developer.apple.com/documentation/uikit/uicontrol
なので、これを解決するためにはViewControllerクラスのプロパティ(インスタンス変数)としてUIImageViewを定義しないといけない気がする。
UIImageViewをViewControllerのプロパティにして(4行目)どこのメソッドからでもさわれるようにした。
class ViewController: UIViewController { let img = UIImage(named: "dog1.png") let img2 = UIImage(named: "dog2.png") let imgView = UIImageView() //UIImageViewをここに宣言。 override func viewDidLoad() { super.viewDidLoad() imgView.image = img //スクリーンサイズ let screenWidth = self.view.frame.width let screenHeight = self.view.frame.height //画像サイズを取得 let imgW = img!.size.width let imgH = img!.size.height print("screen w, h : \(screenWidth), \(screenHeight) img w, h : \(imgW), \(imgH)") //スマホ高さが375で画像が600だと画像を414/600=0.69倍すればよい.スマホ横向きなので高さに合わせる let hScale = screenHeight / imgH //imgViewをとりあえず(0,0)座標から描画し、縦横は倍率で計算してセット、縦横比も保たれる imgView.frame = CGRect(x:0, y:0, width: imgW * hScale, height: imgH * hScale) //imgViewの中央を画面中央とする imgView.center = self.view.center self.view.addSubview(imgView) //普通のボタンを生成 let btn = UIButton(type: UIButtonType.system) //画面いっぱいの大きさにする btn.frame = CGRect(x:0, y:0, width:screenWidth, height:screenHeight) //imgView.addSubview(btn)これだと画像よりボタンが小さくなっちゃうのでだめ self.view.addSubview(btn) //ボタンをタップした際に反応するメソッドを用意 btn.addTarget(self, action: #selector(self.changeImg(_:)), for: UIControlEvents.touchUpInside) } //ボタンをタップしたときの処理で画像を変更する @objc func changeImg(_ sender: UIButton) { imgView.image = img2 }
46行目でimgView.image = img2として犬画像2にチェンジしています。
これで画面をタップしたら画像が切り替わる処理ができました。
ということでUIButtonのaddTargetメソッドの使い方が整理できました。
このアプリはこれだけのアプリです。画像2→画像1へのチェンジはしません。取り急ぎaddTargetメソッドを解説するためだけなので。
さらにaddTargetメソッドを調べてみた
ソースコード上でCommandキーを押しながらメソッド上にマウスを持ってきて定義部分を見ようとしても「?」が出て見れなかった。なのでヘルプからリファレンスを参照しようと思いました。
UIControllクラスのメソッドとして定義されています。
宣言部分は以下のようになっています。
func addTarget(_ target: Any?, action: Selector, for controlEvents: UIControlEvents)
addTargetの引数は以下のように指定します。
target | アクションメソッドが呼ばれるオブジェクトを指定。 |
---|---|
action | 呼ばれるメソッドを記述 |
controlEvents | UIControlEventsの定数を記述 |
UIControlEventsには以下のようなものがあるので確認しておいた。けど全部はどんな意味かわからなかった。
touchDown | コントロール内でのタッチダウン |
---|---|
touchDownRepeat | 複数回のタッチダウンイベント |
touchDragInside | コントロール内で指がドラッグされた |
touchDragOutside | コントロール外で指がドラッグされた |
touchDragEnter | コントロール内へ指がドラッグされた |
touchDragExit | コントロール内から外へドラッグされた |
touchUpInside | コントロール内でタッチアップされた |
touchUpOutside | コントロール外でタッチアップされた |
touchCancel | 不明 |
valueChanged | 値の変化 |
primaryActionTriggered | タッチアップと同じ? |
editingDidBegin | TextFieldでの編集開始 |
editingChanged | TextField内の文字変化や入力 |
editingDidEnd | 画面外タップでのTextFieldの編集終了 |
editingDidEndOnExit | リターンキーによるTextFieldの編集終了 |
allTouchEvents | すべてのタッチイベント |
allEditingEvents | TextFieldに関するイベント |
applicationReserved | 予備だと思う |
systemReserved | 予備だと思う |
allEvents | 全てのイベント |
UIControlクラスを継承しているクラスには何があるか調べてみる。
以下のクラスの継承元がUIControlだった。なのでこれらのクラスではaddTargetメソッドが使えることになる。
・UIDatePicker
・UIPageControl
・UISegmentedControl
・UISlider
・UIStepper
・UISwitch
・UITextField
それからUIControlクラスの継承元はUIViewだった。念の為。