본문 바로가기

DEV

[iOS/Swift] addTarget()과 Objective-C 셀렉터(#selector)

반응형

먼저 #selector는 Objective-C 런타임에서 메서드를 동적으로 호출하기 위해 사용하는 것으로, 주로 addTarget(action:for:) 같은 UIKit 메서드에서 버튼 클릭 등의 이벤트를 특정 메서드와 연결할 때 사용된다.

 

이런건 알고 있었지만 프로젝트 도중 #selector 관련하여 오류가 생겼다.

 

현재 함수에서 .addTarget() action: 부분에 함수 입력 시 매개변수를 받아, 각 버튼 별로 title과 titleColor, backgroundColor를 바꾸고 싶었다.

func addSeriesButtons(_ seriesCount: Int) {
    for num in 1 ... seriesCount {
        let seriesButton: UIButton = {
            let button = UIButton()
            button.setTitle("\(num)", for: .normal)
            button.titleLabel?.font = .systemFont(ofSize: 16, weight: .bold)
            if num == 1 {
                button.setTitleColor(.white, for: .normal)
                button.backgroundColor = .systemBlue
            } else {
                button.setTitleColor(.systemBlue, for: .normal)
                button.backgroundColor = .systemGray5
            }
            button
                .addTarget(
                    self,
                    action: #selector(changeSeries),
                    for: .touchUpInside
                )
            button.layer.cornerRadius = 22
            button.layer.masksToBounds = true
            return button
        }()
        seriesButtonHStack.addArrangedSubview(seriesButton)
        seriesButton.snp.makeConstraints { make in
            make.width.height.equalTo(44)
        }
        if num == 1 {
            selectedButton = seriesButton
        }
    }
}

 

그래서 action: #selector(changeSeries(num))을 한 후,  아래처럼 num을 인자로 보내기 위해 아래와 같이 작성하였지만, 오류가 나버렸다.

@objc func changeSeries(_ num: Int) {
    var seriesNumber = num
    print(seriesNumber)
}

 

 

그래서 Grok한테 물어봤다.

즉, UIButton의 이벤트 핸들러는 액션을 발생시킨 객체를 파라미터로 전달하기에, addTarget으로 연결된 메서드는 반드시

@objc func changeSeries(_ sender: UIButton) 처럼 sender(UIButton 타입의 파라미터)를 인자로 받는 형태여야 한다고 한다.

따라서  action: #selector(changeSeries(_:)) 같은 방식으로 작성을 해야하고, 여기서 changeSeries(_:)는 sender라는 UIButton 타입의 파라미터를 받는 메서드를 의미한다.

 

그럼 왜 changeSeries() 가 아닌 changeSeries(_:) 처럼 표현해야 할까?

그건 그냥 문법이 그런거였다.

Swift에서 #selector를 사용할 때는 함수의 정확한 시그니처를 명시해야 한다고 한다.

changeSeries(_:)에서 _: 는 첫 번째 파라미터가 있다는 것을 나타낸다고 하고, 이름을 생략하더라도 파라미터의 존재를 파악해야한다고 한다.

 

다음과 같이 수정하였다.

// series 개수에 따라 seriesButton 추가
    func addSeriesButtons(_ seriesCount: Int) {
        for seriesNumber in 1 ... seriesCount {
            let seriesButton: UIButton = {
                let button = UIButton()
                button.setTitle("\(seriesNumber)", for: .normal)
                button.titleLabel?.font = .systemFont(ofSize: 16, weight: .bold)
                if seriesNumber == 1 {
                    button.setTitleColor(.white, for: .normal)
                    button.backgroundColor = .systemBlue
                } else {
                    button.setTitleColor(.systemBlue, for: .normal)
                    button.backgroundColor = .systemGray5
                }
                button
                    .addTarget(
                        self,
                        action: #selector(changeSeries(_:)),
                        for: .touchUpInside
                    )
                button.layer.cornerRadius = 22
                button.layer.masksToBounds = true
                return button
            }()
            seriesButtonHStack.addArrangedSubview(seriesButton)
            seriesButton.snp.makeConstraints { make in
                make.width.height.equalTo(44)
            }
            if seriesNumber == 1 {
                selectedButton = seriesButton
            }
        }
    }
    
    // 시리즈 변경 시 해당 내용 load
    @objc func changeSeries(_ sender: UIButton) {
        guard let titleText = sender.title(for: .normal),
              let seriesNumber = Int(titleText) else {
            print("Error: Invalid series number")
            return
        }
        
        var prevButton: UIButton?
        
        if sender != selectedButton {
            prevButton = selectedButton
            selectedButton = sender
            sender.setTitleColor(.white, for: .normal)
            sender.backgroundColor = .systemBlue
            prevButton?.setTitleColor(.systemBlue, for: .normal)
            prevButton?.backgroundColor = .systemGray5
        }
        
        delegate?.loadSelectedSeries(self, didSelectSeries: seriesNumber)
    }
반응형