프로젝트를 진행하면서 테이블 뷰 안에 컬렉션 뷰를 두고, 컬렉션 뷰 셀을 누르면 팝업창이 뜨는 기능을 구현해야 했다. 협업할 때 충돌이 덜 나도록 segue 연결을 코드로만 진행했는데, 이렇게 하니까 자동으로 셀이랑 연결되지 않아서 다른 방법이 필요했다.
처음에는 notification center로 해결했는데 이번에 프로토콜을 공부해가지고 이걸로 해보겠다❗️
우선, 이렇게 테이블 뷰 안에 컬렉션 뷰가 있는 구조의 예제가 있다.
우리가 보통 코드로 segue를 연결하면 아래와 같이 작성한다.
extension TableViewCell: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
guard let vc = UIStoryboard(name: "PopUpView", bundle: nil).instantiateViewController(withIdentifier: "PopUpVC") as? PopUpVC else {
return
}
vc.cellNum = "\(indexPath.row)번째 셀"
vc.modalPresentationStyle = .overFullScreen
vc.modalTransitionStyle = .crossDissolve
present(vc, animated: true, completion: nil)
}
}
그런데 TableViewCell.swift 파일에 작성하면 오류가 발생한다.
왜 그럴까? 🤔 공식 문서를 보면 present 메소드의 위치가 UIViewController 안에 있다는 걸 알 수 있다.
이 말인 즉슨, present는 UIVIewController에 있는 메소드이기 때문에 UITableViewCell만 상속받고 있는 TableViewCell 클래스에서는 사용할 수가 없다는 것❗️
화면 이동은 UIViewController를 상속받는 ViewController 클래스에서 가능하다. 그렇다면 ViewController 클래스와 TableViewCell 클래스가 서로 어떻게 통신할 수 있을까?
이때 사용하는 게 바로 프로토콜이다.
공식문서의 프로토콜 설명을 보면, 프로토콜은 상속에 의해 멀리 떨어져 있는 두 클래스가 특정 목표를 달성하기 위해 서로 통신할 수 있도록 한다고 되어있다.
한쪽 클래스에 프로토콜을 정의하고 다른 쪽 클래스에서 그 프로토콜을 상속받아 메소드를 구현하면 끝❗️
우리 예제를 예로 들면,
TableViewCell 클래스에서 처리하기 곤란한 화면 이동 방법을 ViewController 클래스에 위임(delegate)해주면 된다.
위임할 작업들을 프로토콜에 선언만 해두고, ViewController 클래스에서 이 프로토콜을 채택해서 메소드를 구현하면 TableViewCell 클래스에서 할 일을 ViewController에 떠넘길 수 있는 것이다!
(UITableView, UICollectionView에서 사용한 delegate 패턴을 이용한 거라고 보면 된다.)
글로만 보면 이해가 안되니까 한 번 구현해보자 〰️
TableViewCell 클래스에 프로토콜 선언하기
import UIKit
protocol CVCellDelegate {
func selectedCVCell(_ index: Int)
}
class TableViewCell: UITableViewCell {
...
}
간단하게 프로토콜명과 메소드명만 선언해놓으면 된다.
이 예제는 컬렉션뷰 셀을 눌렀을 때 해당 셀의 인덱스 번호가 표시되는 팝업창을 띄워야 하므로 index를 인자로 넘겨준다.
CVCellDelegate 타입의 변수 선언
class TableViewCell: UITableViewCell {
...
var delegate: CVCellDelegate?
...
}
TableViewCell 안에 CVCellDelegate 타입의 var delegate를 선언해둔다. (프로토콜은 그냥 하나의 Type이라 다른 타입들처럼 사용 가능하다.)
delegate.selectedCVCell 사용
extension TableViewCell: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if let delegate = delegate {
delegate.selectedCVCell(indexPath.item)
}
}
}
셀이 눌릴 때 해당 셀의 인덱스 번호를 표시하는 뷰로 이동하기 위해 func collectionView( ... didSelectItemAt) 메소드 안에서 사용해준다.
그리고 사용할 때 delegate.selectedCVCell(...) 하면 됨. 구현은 ViewController에서 하고 사용은 TableViewCell에서 하는 형태다.
이제 selectedCVCell 메소드를 구현하러 ViewController로 가보자.
CVCellDelegate 프로토콜 채택하여 메소드 구현하기
extension ViewController: CVCellDelegate {
func selectedCVCell(_ index: Int) {
guard let vc = UIStoryboard(name: "PopUpView", bundle: nil).instantiateViewController(withIdentifier: "PopUpVC") as? PopUpVC else {
return
}
vc.cellNum = "\(index)번째 셀"
vc.modalPresentationStyle = .overFullScreen
vc.modalTransitionStyle = .crossDissolve
present(vc, animated: true, completion: nil)
}
}
selectedCVCell 메소드에 평소처럼 segue 연결하는 코드를 작성하면 된다.
ViewController 본인이 대리자임을 알려주기
뷰와 컨트롤러 사이는 블라인드 커뮤니케이션 구조라 서로 아무것도 알지 못한다. ViewController에서 프로토콜을 채택하고 구현했다 해도 말해주지 않으면 아무 일도 일어나지 않는다. 아래와 같이 ViewController 본인이 대리자임을 알려주어야 한다.
cell.delegate = self
이렇게 해서 TableViewCell 클래스가 알게 되는 건 그저 ViewController가 selectedCVCell 메소드를 구현한다는 사실뿐이다. 다른 건 암것두 모름.
✨완성✨
⚡️ 소스 코드 ⚡️
댓글