티스토리 뷰

iOS 앱을 개발하다 보면, 테이블뷰와 컬렉션뷰(이하 테이블뷰)는 거의 필수적으로 만들어야 한다.

 

그러다보면 기획에 따라 동일한 셀을 여러 테이블뷰에 사용하는 것이 필요할 떄도 있고, 

동일한 테이블뷰를 여러 곳에 사용하는 경우도 있다. 

 

이런 경우에 Wireframe을 멍하게 바라보며 고민을 한다.

  • 뷰 컨트롤러 내에서 분기처리 할까?
  • 부모 뷰 컨트롤러로 만들까?
  • 어느 뷰 범위까지 모듈로 만들까?
  • ...

여러가지 전후 상황과 화면 구성에 따라 가장 적절한 방법을 찾기 위해 노력한다. 

 

가장 최근 업무에서는 커스텀 테이블뷰 혹은 컬렉션뷰를 만들어서 사용하는 방법을 사용했다.

 

동일한 테이블뷰를 여러가지 뷰 컨트롤러에서 사용하는 UI였는데,

모든 뷰 컨트롤러에서 dataSource와 delegate를 구현하는 것은 비효율적이라 생각했다.

 

따라서,

1. 하나의 커스텀 테이블뷰를 만들고,

2. 해당 테이블뷰에 뿌려줄 데이터만 바인딩 하면

3. 동일한 셀과 환경을 가진 테이블뷰가 완성되는 방법이다.

 

업무에서 진행했던 상황과 흡사하게 간단한 예제를 만들었고,

빠른 개발을 위해 Snapkit, Rxswift, Kingfisher, Alamofire 라이브러리를 사용했다.

 

1. UI

아래 사진처럼 첫번째 뷰와 두번째 뷰는 전혀 다른 뷰다. 

(굉장히 비슷해 보이지만, 하여튼 전혀 다른 뷰임...)

(유저는 인공으로 만든 더미데이터임)

 

서로 다른 뷰 컨트롤러에 붙은 커스텀 테이블뷰

 

첫번째 뷰에서 사용했던 동일한 유저 리스트 테이블 뷰를 다른 뷰 컨트롤러에 붙일 경우를 가정했다.

 

class FirstViewModel {
    //MARK: - Properties
    // 초기값 빈 배열
    let userList = BehaviorRelay<[UserResult]>(value: [])
    let network = UserNetworkManager()
    
    //MARK: - Methods
    // API Call
    func requestUserList() {
        self.network.requestUserList { userList in
            self.userList.accept(userList)
        }
    }
}

class FirstController: UIViewController {
    //MARK: - Properties
    let viewModel = FirstViewModel()
    lazy var tableView = MyListTableView(userListRelay: self.viewModel.userList)
    
    //MARK: - Lifecycle
    override func viewDidLoad() {
        super.viewDidLoad()
        setupUI()
        setupNavigation()
        self.viewModel.requestUserList()
    }
    
    //MARK: - Methods
    private func setupUI() {
        self.view.backgroundColor = .white
        
        self.view.addSubview(tableView)
        tableView.snp.makeConstraints {
            $0.edges.equalTo(view.safeAreaLayoutGuide)
        }
    }
    
    private func setupNavigation() {
        self.navigationItem.title = "첫번째 뷰"
    }
}

 

2. 뷰 컨트롤러 & 뷰 모델

FirstViewController와 FirstViewModel이 있다.

뷰모델에서는 userList라는 RxSwift의 relay를 가지고 있으며, 초기값은 빈 배열로 선언했다.

네트워킹을 통해 얻은 유저의 데이터를 userList에 accept 해준다.

 

뷰 컨트롤러에서는 커스텀 테이블뷰인 MyListTableView를 지연저장 프로퍼티로 선언하면서,

뷰모델의 userList 배열을 생성자 함수의 파라미터로 넣어준다.

 

That's it ! 끝이에요 🔥

 

사용하는 뷰 컨트롤러와 뷰모델의 입장에서는 테이블뷰에 데이터만 바인딩해주면 더이상 할게 없다. 

그렇다면 테이블뷰 내부는 어떻게 구현되어 있을까?

 

3. 커스텀 테이블뷰

테이블뷰 내부도 필수적인 코드 자체는 간단하게 구현이 가능하다

class MyListTableView: UITableView {
    //MARK: - Properties
    let userList: BehaviorRelay<[UserResult]>
    private let disposeBag = DisposeBag()
    //MARK: - Lifecycle
    init(userListRelay: BehaviorRelay<[UserResult]>) {
        self.userList = userListRelay
        super.init(frame: .zero, style: .plain)
        self.register(UINib(nibName: UserCell.identifier, bundle: nil), forCellReuseIdentifier: UserCell.identifier)
        self.rowHeight = UITableView.automaticDimension
        self.bind()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    //MARK: - Methods
    private func bind() {
        // 데이터 바인딩
        self.userList
            .bind(to: self.rx.items) { tableView, row, item in
                guard let cell = tableView.dequeueReusableCell(withIdentifier: UserCell.identifier, for: IndexPath(row: row, section: 0)) as? UserCell else { return UITableViewCell() }
                cell.selectionStyle = .none
                cell.user = item
                return cell
            }
            .disposed(by: disposeBag)
        
        // 탭 이벤트
        self.rx.modelSelected(UserResult.self)
            .bind { result in
                print("👉\(result.name.first) 선택됨")
            }
            .disposed(by: disposeBag)
    }
}

생성자 함수에서 받은 userListRelay를 프로퍼티에 넣어주고, 셀의 등록을 포함한 테이블 뷰의 상세 코드를 작성한다.

생성과 함께 bind() 함수를 통해 userList를 테이블뷰에 바인딩한다. 

위 코드는 편의를 위해 RxCocoa를 통해 구현되었지만, DataSource를 이용해도 아무 상관이 없다.

 

실제 업무에서 작업한 코드를 필수적인 부분만 예제로 구현했는데, 코드가 더 복잡해져도 본질은 다음과 같다.

  • UserListTableView는 자신을 그리는 뷰가 어떤것인지 관심도 없고 알지도 못함.
  • 그냥 UserList를 바인딩 했을 뿐이고, 
  • UserList에 데이터가 들어오면 뿌려주기만 하면 됨

--

이렇게 이곳저곳에 붙일 수 있는 테이블뷰를 간단하게 만들어 보았다. 

실제 구현할 때는 여러가지 상황에 따른 다양한 고려가 필요할 것이다.

 

소스코드:

https://github.com/jisu15-kim/examples/tree/main/ExampleReuseTableView

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/02   »
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
글 보관함