개발/iOS

[UIButton] .addTarget VS .addAction

baerogramming 2025. 2. 6. 15:52

우리는 UIKit 프레임워크에서 버튼을 구현할 때 일반적으로 UIButton을 사용한다.

 

물론 디자인이나 의도하는 사용자 경험에 따라 뷰에 탭 제스처를 붙이기도 하고, 다양한 방법으로 구현하는 경우도 있다.

 

오늘은 버튼의 스타일이나 디자인보다는 동작에 대해 정리를 해보려고 한다.

 

UIButton의 동작을 정의하는 두 가지 방법 addTarget 메서드와 addAction 메서드를 비교해 보자.

 


 

AddTarget

우선 한 가지 짚고 넘어갈 사실은, 두 메서드 모두 UIButton이 아닌 UIControl(* UIButton은 UIControl을 상속한다.)의 메서드이다.

 

iOS 2.0은 2008년 출시했다. 고2 아래로는 존댓말을 쓰도록 하자.

우리에게 익숙한 addTarget 메서드의 정의를 보면, target과 Selector, controlEvents를 받는 것을 볼 수 있다.

 

나를 포함한 많은 개발자들은 그저 순서대로 ViewController, Objective Function, Control Event를 넘겨주고 사용하고 있을 수 있다. Documentation의 설명을 한번 긁어와 봤다.


Parameters 

target

The target object—that is, the object whose action method is called. If you specify nil, UIKit searches the responder chain for an object that responds to the specified action message and delivers the message to that object.

 

action

A selector identifying the action method to be called. You may specify a selector whose signature matches any of the signatures in Listing 1. This parameter must not be nil.

 

controlEvents

A bitmask specifying the control-specific events for which the action method is called. Always specify at least one constant. For a list of possible constants, see UIControl.Event.


콜아웃에 쓰고 싶었는데 콜아웃에는 강조가 안 되는 거 같다.

 


 

Target

타겟부터 보면 Action Method, 즉 우리가 지정한 메서드를 호출하는 객체를 받는 패러미터이다.

 

관습적으로 self를 붙여 사용하고는 하는데, 만약 nil값을 주면 응답 체인에서 적절한 객체를 찾는다고 한다.

 

조금 더 자세히 말하면 자기 자신을 포함한 계층에서부터 올라가다가 Action Method를 구현한 첫 번째 객체에서 실행된다.

 

다음과 같은 간단한 구조라면 문제없이 실행되는 것이다.

import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        
        let button = UIButton(type: .system)
        button.setTitle("테스트 버튼", for: .normal) // Button Configuration에 대해서도 추후에 다룰 예정
        button.addTarget(nil, action: #selector(buttonTaped), for: .touchUpInside)
        
        view.addSubview(button)
        button.center = view.center
    }
    
    @objc func buttonTaped() {
        print("Button tapped in ViewController!")
    }
    
}

 

그러나 묵시적으로 둔다는 건 곧 에러를 야기할 소지를 남긴다는 것과 같다고 생각하기 때문에 평소 사용하던 대로 self를 붙여 사용하면 될 것 같다. 

 

단지 당연하게만 생각하고 써 왔던 것에 대해 '왜?'라는 의문을 한번 던져보았다.

 

Action

아마 이 부분이 최신 Swift와 가장 동떨어진 부분이 아닐까 싶다. Action Parameter는 Selector라는 구조체를 받는다.

 

Selector는 무려 iOS의 탄생부터 함께해 온 친구이다. 다시 말해 Objective-C에서 사용하던 녀석이고, 런타임에서 메서드를 실행시키는 방식이라고 한다. 사용은 우리가 아는 바와 같이   #selector(@objc method) 로 사용한다. 

 

이 부분이 내가 AddAction 메서드를 즐겨 쓰게 만든 요인이다. 많은 서비스에서 레퍼런스로 잡는 카카오톡의 최소 버전이 iOS 16.0이니 이제는 AddAction 메서드를 사용해도 문제없지 않을까?라는 생각이다. (물론 완벽하게 지양하자는 아니다! 레거시 프로젝트에 투입되면 당연히 그에 맞춰야 한다고 생각한다.

 

 

ControlEvents

요즘 부쩍 보이는 키워드이다. RxSwift를 열심히 공부 중이다 보니 이제는 침대 머리맡에서 나와도 '뭐야 이게 왜 여기 있어? 뷰모델에 안 있고' 할 것 같다.

 

해당 Parameter는 버튼의 어떤 이벤트를 받았을 때 Action을 실행하는지를 지정한다.

 

다들 한 번쯤 touchDown이 아니라 왜 touchUpInside를 사용하지? 와 같은 의문을 품을 수 있을 거라고 생각한다. 이는 사용자 경험과 관련이 있다.

 

혹시 앱 사용 중 무심코 버튼을 잘못 눌렀을 때, 손을 떼지 않고 그대로 손가락을 다른 영역으로 옮긴 후 떼 본 경험이 있지 않은가? 우리는 경험적으로 이 동작이 버튼 클릭을 캔슬하는 방법이라는 걸 알고 있다.

 

만약 버튼의 ControlEvent가 touchDown으로 되어 있었다면, 우리가 손을 떼는 영역과는 상관없이 버튼에 손이 닿는 순간 Action이 실행될 것이다. 이는 사용자 경험을 해칠 수 있고, 앱의 사용률 떨어트릴 수 있다.(실제로 본인도 앱 사용 중 이러한 부분을 찾게 되면 사용이 굉장히 꺼려진다.)

 

이러한 내용은 비단 앱뿐만이 아니라 사용자와 상호작용하는 프레임워크에서 모두 동일하다고 한다.

 

정리

AddAction으로 넘어가기 전 간단히 정리해 보자면,

  1. AddTarget 메서드는 iOS 2부터 사용된 아주 오래된 메서드이다.
  2. 때문에 Action Method를 구현할 때 @objc Attribute를 붙이고, Selector를 사용해 버튼에 넘겨주어야 한다.
  3. touchDown을 안 쓰는 이유는 사용자가 일반적으로 예상할 수 있는 동작이 아니기 때문이다.

 


 

AddAction

마참내! 내가 사랑하는 AddAction.

 

AddAction은 iOS 14에서 추가된 UIControl의 메서드로, 버튼의 Action을 클로져 형태로 넘겨줄 수 있다. 

 

문서 서머리만 봐도 짧다.

 

AddAction의 Parameter를 보면, 단순히 action과 controlEvents만을 받는다. 얼마나 간단하면 Xcode 퀵헬프에 어떠한 부연 설명도 없다.

 

UIAction은 iOS 13부터 사용 가능한 A menu element that performs its action in a closure.  라고 한다.(출처 - UIAction)

 

아 너무 심플하다

 

이건 AddAction의 Initializer입니다. 머야 심플하다면서요 AddTarget 돌려줘요.

???

 

진정하고 찬찬히 봐 보자. 어렵지 않다.

 

이니셜라이저가 길다는 건 할 수 있다는 게 많다는 말도 된다.

 

잘 보면 타이틀, 이미지, 구분자 등등 뭐가 많지만,, 우리가 써야 할 건 handler 단 하나뿐이다.

 

오늘도 고마워 너굴맨!

 

기본 사용법은 간단하지만 다양한 옵션도 고려할 수 있게 해 준 너굴,,, 아니 애플 디벨로퍼들에게 감사하도록 하자.

 

위의 코드를 다시 가져와서, AddAction 메서드를 적용해 본다면, 다음과 같을 것이다.

 

import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        
        let button = UIButton(type: .system)
        button.setTitle("테스트 버튼", for: .normal) // Button Configuration에 대해서도 추후에 다룰 예정
        button.addAction ( UIAction { [weak self] _ in
        	print("Button tapped in ViewController!")
        }, for: .touchUpInside)
        
        view.addSubview(button)
        button.center = view.center
    }
    
}

 

훨씬 낫지 않은가? 물론 로직이 길어진다거나 버튼 자체가 많아져 tag를 이용해 구분해야 할 때, 또는 버튼 자체를 동적으로 생성해야 하는 경우에는 전통적인 AddTarget 방식이 더 나을 수 있다.

 

그러나 현재 Swift 코드 스타일에 더 어울리고 클로져로 간편하게 Action을 지정할 수 있다는 점이 내게는 매력적으로 다가온 것 같다.

 

여러분은 어떤 스타일을 더 선호하는가?