티스토리 뷰

 

동적으로 버튼을 생성해야 할 경우, selector를 미리 선언해줘야 하기 때문에 이벤트를 추가하는데 어려움이 있을 수 있습니다.

이를 위해 AssociatedObject 를 이용하여 UIButton에 closure를 추가하는 방법에 대해 정리해 보았습니다.

 

아래는 AssociatedObject에 대해 간단히 정리한 내용입니다~ 👇👇👇

 

[swift] AssociatedObject

AssociatedObject에 대해 간단히 정리해 보았습니다!! AssociatedObject는 런타임시 기존 클래스에 SubClassing 없이 사용자 정의 속성을 연결(추가) 할 수 있습니다. 아래는 AssociatedObject 관련 함수입니다. /..

jintaewoo.tistory.com

 

다음 예제는 UIButton을 확장하여 Action으로 closure를 추가할 수 있게 만드는 코드입니다.

extension UIBUtton {
    public typealias UIBUttonTargetClosure = (UIBUtton) -> ()
    
    // 1
    private class UIBUttonClosureWrapper: NSObject {
        let closure: UIBUttonTargetClosure
        init(_ closure: @escaping UIBUttonTargetClosure) {
            self.closure = closure
        }
    }
    
    private struct AssociatedKeys {
        static var targetClosure = "targetClosure"    // 2
    }
    
    private var targetClosure: UIBUttonTargetClosure? {
        get {
            // 3
            guard let closureWrapper = objc_getAssociatedObject(self, &AssociatedKeys.targetClosure) as? UIBUttonClosureWrapper else { return nil }
            return closureWrapper.closure
        }
        set(newValue) {
            guard let newValue = newValue else { return }
            // 4
            objc_setAssociatedObject(self,
                                     &AssociatedKeys.targetClosure,
                                     UIBUttonClosureWrapper(newValue),
                                     objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        }
    }
    
    // 5
    @objc func closureAction() {
        guard let targetClosure = targetClosure else { return }
        targetClosure(self)
    }
    
    // 6
    public func addAction(for event: UIButton.Event, closure: @escaping UIButtonTargetClosure) {
        targetClosure = closure
        addTarget(self, action: #selector(UIButton.closureAction), for: event)
    }
}


// How To Use
let button = UIButton()
button.addAction(for: .touchUpInside) { button in
    // Do Something
}

번호 순서대로 간략히 설명하면, 

  1. closure를 저장할 wrapper 클래스 입니다.
  2. 값을 저장하기 위해 선언된 키입니다.
  3. objc_getAssociatedObject 메소드를 사용하여 키에 저장된 값을 불러 옵니다.
  4. objc_setAssociatedObject 메소드를 사용해 키에 값을 저장합니다.
  5. 저장된 closure를 가져오는 메소드 입니다.
  6. addTarget를 wrapping하여 버튼에 closure를 설정하는 메소드 입니다.

 


 

UIButton에 대해 한정적이기 보다, 상속받고 있는 UIControl를 확장하여 여러 곳에서 사용할 수 있게 할 수 있습니다.

UIControl은 UIButton, UIPageControl, UISlider 등 여러 곳에서 상속받는 클래스이기 때문에, UIControl을 확장하면 조금 더 효율적으로 사용할 수 있습니다.

 

아래 링크에서 UIControl을 상속받는 클래스들을 볼 수 있습니다.

 

Apple Developer Documentation

 

developer.apple.com

 

아래는 UIButton을 확장한 코드에서 UIButton 대신 UIControl로 변경만 해준 코드입니다.

extension UIControl {
    public typealias UIControlTargetClosure = (UIControl) -> ()
    
    private class UIControlClosureWrapper: NSObject {
        let closure: UIControlTargetClosure
        init(_ closure: @escaping UIControlTargetClosure) {
            self.closure = closure
        }
    }
    
    private struct AssociatedKeys {
        static var targetClosure = "targetClosure"
    }
    
    private var targetClosure: UIControlTargetClosure? {
        get {
            guard let closureWrapper = objc_getAssociatedObject(self, &AssociatedKeys.targetClosure) as? UIControlClosureWrapper else { return nil }
            return closureWrapper.closure
        }
        set(newValue) {
            guard let newValue = newValue else { return }
            objc_setAssociatedObject(self,
                                     &AssociatedKeys.targetClosure,
                                     UIControlClosureWrapper(newValue),
                                     objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
	        }
    }
    
    @objc func closureAction() {
        guard let targetClosure = targetClosure else { return }
        targetClosure(self)
    }
    
    public func addAction(for event: UIControl.Event, closure: @escaping UIControlTargetClosure) {
        targetClosure = closure
        addTarget(self, action: #selector(UIControl.closureAction), for: event)
    }
    
}


// How To Use
let button = UIButton()
button.addAction(for: .touchUpInside) { button in
    // Do Something
}

let slider = UISlider()
slider.addAction(for: .valueChanged) { slider in
    // Do Something
}

let pageControl = UIPageControl()
pageControl.addAction(for: .valueChanged) { pageControl in
    // Do Something    
}

 

마지막으로 UIBarButtonItem에 closure를 추가하는 코드입니다.

extension UIBarButtonItem {
    public typealias UIBarButtonItemTargetClosure = (UIBarButtonItem) -> ()

    private class UIBarButtonItemClosureWrapper: NSObject {
        let closure: UIBarButtonItemTargetClosure
        init(_ closure: @escaping UIBarButtonItemTargetClosure) {
            self.closure = closure
        }
    }
    
    private struct AssociatedKeys {
        static var targetClosure = "targetClosure"
    }
    
    private var targetClosure: UIBarButtonItemTargetClosure? {
        get {
            guard let closureWrapper = objc_getAssociatedObject(self, &AssociatedKeys.targetClosure) as? UIBarButtonItemClosureWrapper else { return nil }
            return closureWrapper.closure
        }
        set(newValue) {
            guard let newValue = newValue else { return }
            objc_setAssociatedObject(self,
                                     &AssociatedKeys.targetClosure,
                                     UIBarButtonItemClosureWrapper(newValue),
                                     objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        }
    }

    @objc func closureAction() {
        guard let targetClosure = targetClosure else { return }
        targetClosure(self)
    }
    
    convenience init(title: String?, style: UIBarButtonItem.Style, closure: @escaping UIBarButtonItemTargetClosure) {
        self.init(title: title, style: style, target: nil, action: nil)
        targetClosure = closure
        action = #selector(UIBarButtonItem.closureAction)
    }
    
    convenience init(image: UIImage, style: UIBarButtonItem.Style, closure: @escaping UIBarButtonItemTargetClosure) {
        self.init(image: image, style: style, target: nil, action: nil)
        targetClosure = closure
        action = #selector(UIBarButtonItem.closureAction)
    }
}


// How To Use
let imageBarButton = UIBarButtonItem(image: UIImage(), style: .plain) { barButtonItem in
    // Do Something    
}

let confirmBarButton = UIBarButtonItem(title: "확인", style: .plain) { barButtonItem in
    // Do Something    
}

UIBarButtonItem은 초기화할 때 action을 추가하기 때문에 초기화 메소드를 wrapping한 메소드를 추가하였고, 나머지는 UIControl을 확장한 코드와 같습니다. 🎉🎉🎉

 


 

- Reference

 

 

Adding Closures to Buttons in Swift

One of the things I still find a little backward and unnatural to use in Swift is the Target-Action pattern.

getswifty.dev

 

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/04   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
글 보관함