iOS 공부하는 감자

iOS) ViewController에서 TableViewCell 내부의 Button Action 처리하기 본문

iOS

iOS) ViewController에서 TableViewCell 내부의 Button Action 처리하기

DongTaTo 2022. 7. 27. 20:37
반응형

Cell 내부에 구현된 버튼의 Touch Event를 ViewController에서 처리하는 것이 좋은 이유

  • Event 핸들링은 ViewController에서 처리하는 것이 유지보수에 좋다.
  • 버튼의 Touch Event로 Data처리가 필요한 경우, ViewController를 통해 Model의 Data에 접근하여 처리할 수 있다.
  • TableViewCell 파일에 IBAction으로만 기능을 구현하면, 모든 Cell의 버튼이 동일한 기능을 수행하므로 각 Cell별로 분기처리가 필요한 경우 구현하기 까다롭다.

 

다음의 3가지 방법을 통해 Cell에 있는 Button Action을 ViewController에서 처리할 수 있다.

  1. Button에 Tag를 부여해서 처리
  2. CallBack 함수(클로저)를 사용하여 처리
  3. Delegate 패턴을 사용하여 처리

 

 


 

1. Button에 Tag를 부여해서 처리

각 Cell의 Button이 클릭되면 checkBoxButtonTapped(sender: UIButton) 메서드를 호출하도록 설정한다.

매개변수로 받는 sender(UIButton)의 tag를 사용하여 필요한 데이터 처리를 할 수 있다.

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    guard let cell = tableView.dequeueReusableCell(withIdentifier: "TableViewCell", for: indexPath) as? TableViewCell else {
        return UITableViewCell()
    }
    
    cell.checkBoxButton.tag = indexPath.row
    cell.checkBoxButton.addTarget(self, action: #selector(checkBoxButtonTapped(sender:)), for: .touchUpInside)

    return cell
}



// ================================================================

@objc func checkBoxButtonTapped(sender: UIButton) {
    print("\(sender.tag) 버튼의 Tag로 index값을 받아서 데이터 처리")
}

 

 

 

 

2. CallBack 함수(클로저)를 사용하여 처리

1) TableViewCell에 클로저를 저장할 수 있는 Optional Stored Property를 추가한다.

class TableViewCell: UITalbeViewCell {

    var callBackMehtod: (() -> Void)?
    
    // ...
}

 

2) Cell의 버튼을 IBAction으로 연결하고, Tap되면 콜백함수를 실행하도록 설정한다.

// Cell 내부의 버튼 IBAction으로 연결
@IBAction func checkBoxButtonTapped(_ sender: UIButton) {
    callBackMehtod?()
}

 

 

3) ViewController의 cellForRowAt()에서 cell의 버튼이 Tap되면 수행할 작업을 클로저로 넘겨준다.

  • 강한 순환 참조를 방지하기 위해 클로저에서 weak 또는 unowned를 사용하여 self를 캡처한다.
  • 참조관계 : ViewController > TableView > TableViewCell > closure > ViewController
  • 참조관계를 보면 Cell에서 버튼 Event를 받을 때, ViewController가 메모리에 있을 것이 확실하기 때문에 unowned을 사용하여 옵셔널 바인딩 과정을 생략해도 된다.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    guard let cell = tableView.dequeueReusableCell(withIdentifier: "TableViewCell", for: indexPath) as? TableViewCell else {
        return UITableViewCell()
    }
        
    cell.callBackMehtod = { [weak self] in
        let index = indexPath.row   
        print("\(index) Call Back Method")
    }
    
    return cell
}

 

 

 

 

 

3) Delegate 패턴을 사용하여 처리

1) Delegate Protocol을 생성한다.

protocol ButtonTappedDelegate {
    func cellButtonTapped()
}

 

 

2) TableViewCell 파일에서 Protocol타입을 저장할 수 있는 저장 프로퍼티를 생성한다.

class TableViewCell: UITableViewCell {
	
    // ⭐️ ViewController와 강한 참조 순환이 발생하기 때문에 반드시 weak로 선언!!
    weak var delegate: ButtonTappedDelegate?
    
    // ....
}

 

 

3) ViewController에서 생성한 Protocol을 채택하고 요구사항 메서드를 구현한다.

extension ViewController: ButtonTappedDelegate {
    func cellButtonTapped() {
        print("Button Tapped")
    }
}

 

 

4) ViewController의 cellForRowAt()에서 cell의 저장 프로퍼티 delegate에 self(VC)를 할당한다.

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    guard let cell = tableView.dequeueReusableCell(withIdentifier: "TableViewCell", for: indexPath) as? TableViewCell else {
        return UITableViewCell()
    }

    cell.delegate = self
        
    return cell
}

 

 

5) Cell 내부의 Button Event가 발생하면 Delegate 프로퍼티에 접근하여 요구사항 메서드를 호출한다.

@IBAction func checkBoxButtonTapped(_ sender: UIButton) {
    delegate?.cellButtonTapped()
}

 

 


 

각 방법들의 비교

 

Tag를 사용하는 방법 : 

가장 간단하게 해결할 수 있지만, Tag 속성은 앱에서 뷰객체를 고유하게 식별하기 위한 용도로 사용된다.

만약 위 상황에서 TableView의 Section이 2개 이상 존재한다면 각 Cell의 Tag값이 중복되면서 문제가 발생할 수 있다.

그리고 각 Tag 숫자가 무엇을 의미하는지 직관적으로 이해하기 어렵다.

 

 

Closure를 사용하는 방법 :

저장 프로퍼티로 전달하는 클로저에서 self(ViewController)를 강하게 참조한다면 강한 순환 참조가 발생하여 메모리 누수가 발생할 가능성이 있으므로 유의해서 사용해야 한다.

Tag를 사용하는 방식과 마찬가지로 사용이 간편하지만, 모든 Cell에서 각각 클로저 변수를 저장(참조)하기 때문에 Cell이 많다면 많은 메모리를 차지하는 문제가 발생한다.

클로저의 메모리 저장 방식 :
클로저는 클래스와 마찬가지로 참조 타입으로, 메모리의 Heap 영역에 저장된다. (오래 저장할 필요가 있는 경우)
Heap메모리의 경우 데이터를 저장할 때 동적할당을 하므로 구조적으로 느리게 동작한다.
각 Cell에서 클로저 변수를 저장하면, n개의 cell이 존재할 때, 메모리의 Heap 영역에도 n개의 클로저 인스턴스가 생성된다.

 

 

Delegate 패턴을 사용하는 방법 :

Cell에서 필요한 시점에, Delegate의 요구사항 메서드를 호출할 수 있다.

결국 Protocol을 채택한 ViewController가 필요한 기능을 위임받아서 수행하는 것이기 때문에 메모리가 비효율적으로 사용될 염려도 없고, 각 Cell의 버튼 Tag가 꼬이는(?) 문제도 예방할 수 있다.

 

 

 

 

 

 

 

 

 

 

반응형

'iOS' 카테고리의 다른 글

iOS) nib, awakeFromNib  (0) 2022.08.14
iOS) Run Loop  (0) 2022.08.08
iOS) CollectionView - FlowLayout  (0) 2022.07.20
iOS) ATS (App Transport Security)  (0) 2022.07.20
iOS) UserDefaults  (0) 2022.07.16