clamp
Clamp
clamp
글쓰기 관리
전체 방문자
오늘
어제
  • 분류 전체보기 (509)
    • IOS (85)
    • SwiftUI+TCA+Combine (9)
    • RxSwift + MVVM (56)
    • Clean Architecture (12)
    • SWIFT (56)
    • iOS - TDD (2)
    • 디자인패턴 (4)
    • CS (56)
      • 알고리즘 (29)
      • 운영체제 (15)
      • 자료구조 (2)
      • 네트워킹 (4)
      • 기타 (6)
    • 회고 (0)
    • Firebase (18)
    • SwiftUI (10)
    • iOS - UIKit (11)
    • iOS - 오픈소스 (6)
    • 코딩테스트 (166)
      • 프로그래머스 (164)
    • 정보처리기사 (14)
    • GitHub (2)
글쓰기 / 관리자

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • Q
  • Swift
  • ㅅ
  • uikit

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
clamp

Clamp

RxSwift + MVVM

[RxSwift] CollectionView또는 TableView 셀의 UIImageView or UIButton 클릭 이벤트 처리

2023. 8. 1. 21:28

UITableViewCell의 ImageView 클릭이벤트 처리

  • Cell에서 ImageView의 탭제스처 감지 후 옵저버블로 방출
  • ViewController에서는 옵저버블 구독 후, ViewModel로 전달
  • ViewModel은 이벤트를 받아 처리하고, 필요한 데이터 업데이트 수행

 

방법 1. ViewController에서 바로 ViewModel로 전달

Cell

import UIKit
import RxSwift
import RxCocoa

class MyTableViewCell: UITableViewCell {
    @IBOutlet weak var myImageView: UIImageView!
    
    private var disposeBag = DisposeBag()
    private var indexPath: IndexPath?
    private var imageViewTapSubject = PublishSubject<IndexPath>()
    
    var imageViewTap: Observable<IndexPath> {
        return imageViewTapSubject.asObservable()
    }
    
    override func awakeFromNib() {
        super.awakeFromNib()
        
        let tapGesture = UITapGestureRecognizer(target: self, action: #selector(imageViewTapped))
        myImageView.addGestureRecognizer(tapGesture)
        myImageView.isUserInteractionEnabled = true
    }
    
    func configure(with item: Item, indexPath: IndexPath) {
        self.indexPath = indexPath
        // Cell의 데이터를 설정하는 로직
    }
    
    @objc private func imageViewTapped() {
        guard let indexPath = indexPath else {
            return
        }
        imageViewTapSubject.onNext(indexPath)
    }
    
    override func prepareForReuse() {
        super.prepareForReuse()
        disposeBag = DisposeBag()
    }
}

 

 

ViewController

import UIKit
import RxSwift

class MyViewController: UIViewController {
    @IBOutlet weak var tableView: UITableView!
    
    private var disposeBag = DisposeBag()
    private var viewModel: MyViewModel!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupTableView()
        bindViewModel()
    }
    
    private func setupTableView() {
        tableView.delegate = self
        tableView.dataSource = self
        tableView.register(UINib(nibName: "MyTableViewCell", bundle: nil), forCellReuseIdentifier: "MyTableViewCell")
    }
    
    private func bindViewModel() {
        viewModel = MyViewModel()
        
        viewModel.items
            .bind(to: tableView.rx.items(cellIdentifier: "MyTableViewCell", cellType: MyTableViewCell.self)) { [weak self] row, item, cell in
                guard let indexPath = self?.tableView.indexPath(for: cell) else {
                    return
                }
                cell.configure(with: item, indexPath: indexPath)
                cell.imageViewTap
                    .bind(to: self!.viewModel.imageViewTap)
                    .disposed(by: cell.disposeBag)
            }
            .disposed(by: disposeBag)
        
        viewModel.handleImageViewTap
            .subscribe(onNext: { [weak self] indexPath in
                self?.handleImageViewTap(indexPath: indexPath)
            })
            .disposed(by: disposeBag)
        
        viewModel.fetchData()
    }
    
    private func handleImageViewTap(indexPath: IndexPath) {
        // 해당 IndexPath에 대한 처리 로직 구현
    }
}

extension MyViewController: UITableViewDelegate {
    // TableView의 delegate 메서드 구현
}

ViewModel

import RxSwift

class MyViewModel {
    private let disposeBag = DisposeBag()
    private let itemsSubject = BehaviorSubject<[Item]>(value: [])
    private let imageViewTapSubject = PublishSubject<IndexPath>()
    
    var items: Observable<[Item]> {
        return itemsSubject.asObservable()
    }
    
    var handleImageViewTap: Observable<IndexPath> {
        return imageViewTapSubject.asObservable()
    }
    
    func fetchData() {
        // 데이터를 가져오는 비즈니스 로직
        // itemsSubject.onNext(updatedItems) 호출하여 데이터 업데이트
    }
    
    // TableView의 didSelectRow 메서드를 호출하여 ImageView 클릭 이벤트를 전달받음
    func didSelectRow(at indexPath: IndexPath) {
        imageViewTapSubject.onNext(indexPath)
    }
}

 

방법 2. ViewController의 Subject로 전달 후 ViewModel로 전달( InOut 패턴 적용에 용이하며 UIUpdate같은  다른 요소에도 활용하기 용이.)

 

import UIKit
import RxCocoa
import RxKeyboard
import RxSwift
import SnapKit
import Then

final class ChatViewController: UIViewController {

    private lazy var collectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout()
    ).then {
      // ...생략
    }
    
    private let disposeBag = DisposeBag()
    private let viewModel: ViewModel
    
    //// ⭐️⭐️⭐️⭐️
    private let selectedUser = PublishSubject<User>()
    private let menuSelected = PublishSubject<Menu>()
	//// ⭐️⭐️⭐️⭐️
    
    init(viewModel: ViewModel) {
        self.viewModel = viewModel
        super.init(nibName: nil, bundle: nil)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    // MARK: - Lifecycles
    
    override func viewDidLoad() {
        super.viewDidLoad()
        bind()
        layout()
        configure()
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        navigationController?.isNavigationBarHidden = false
    }

    // MARK: - ViewController Methods
    
    private func bind() {
        let input = ViewModel.Input(
            // ... 중간 생략
            //// ⭐️⭐️⭐️⭐️
            selectedUser: selectedUser
                .throttle(.seconds(1), scheduler: MainScheduler.asyncInstance),
            menuSelected: menuSelected.asObservable()
            //// ⭐️⭐️⭐️⭐️
        )
        
        let output = viewModel.transform(input: input)
        bindCollection(output: output)
        
    }
    // MARK: - Binds
    
    func bindCollection(output: ViewModel.Output) {
        output.messages
            .drive(collectionView.rx.items) { [weak self] collectionView, index, chat in
                guard let self = self,
                      let cell = collectionView.dequeueReusableCell(
                    withReuseIdentifier: Cell.identifier,
                    for: IndexPath(row: index, section: 0)) as? ChatCell else {
                    return UICollectionViewCell()
                }
                cell.layoutChat(chat: chat)
                
                //// ⭐️⭐️⭐️⭐️
                cell.profileImageButton.rx.tap
                    .compactMap { chat.user }
                    .bind(to: self.selectedUser)
                    .disposed(by: self.disposeBag)
                
                cell.menuSelected
                    .bind(to: self.menuSelected)
                    .disposed(by: self.disposeBag)
                //// ⭐️⭐️⭐️⭐️
                
                return cell
            }
            .disposed(by: disposeBag)
}

extension ChatViewController: UICollectionViewDelegateFlowLayout {
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        
    }
}

 

저는 방법 2를 선호하는 편이긴 합니다.. 혹시 더 좋은 방법이 있다면 알려주세요 !
저작자표시 비영리 동일조건 (새창열림)
    'RxSwift + MVVM' 카테고리의 다른 글
    • [RxSwift] throttle, debounce
    • [RxSwift] RxDataSource로 CollectionView 사용하기
    • RxSwift를 이용한 method 호출 구독
    • [Rx + MVVM] Input과 Output을 사용한 MVVM
    clamp
    clamp
    주니어 iOS개발자의 발악!!!!!!!

    티스토리툴바