【Swift】addTargetメソッドの使い方、引数selector、senderのおさらい

テックキャンプ

テックキャンプでiPhoneアプリの学習を進めています。Lesson5辺りまで来て自分が作りたい簡単なアプリが作れそうな気がしています。

しかし学習内容が非常に多くてメソッドやクラスを自分の頭にインプットしきれません。ここらで重要なメソッドを整理しておこうと思いました。

今日は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だった。念の為。

タイトルとURLをコピーしました