From 41209c2e78108072c7d2a461e0b3b59afb494465 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=8B=E1=85=B2=E1=84=8C=E1=85=A2=E1=84=92=E1=85=A9?= =?UTF-8?q?=E1=86=BC?= Date: Wed, 12 Nov 2025 13:44:40 +0900 Subject: [PATCH 1/4] =?UTF-8?q?refactor=20#128:=20delegate=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0=20=EB=B0=8F=20Relay/Signal=20=EA=B8=B0=EB=B0=98=20?= =?UTF-8?q?=EC=9D=B4=EB=B2=A4=ED=8A=B8=20=EC=A0=84=EB=8B=AC=20=EA=B5=AC?= =?UTF-8?q?=EC=A1=B0=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - CompletePage delegate 제거 - DogetherButton에 PublishRelay 추가 (탭 이벤트 방출) - CompletePage에서 Signal로 외부 이벤트 노출 - CompleteViewController에서 emit(onNext:)로 이벤트 처리 - delegate 의존 제거로 단방향 Rx 이벤트 플로우 정리 # Conflicts: # dogether/Presentation/Common/DogetherButton.swift --- .../Base/BaseViewController.swift | 6 +++- .../Presentation/Common/DogetherButton.swift | 13 ++++++- .../Common/JoinCodeShareButton.swift | 13 +++++++ .../Complete/CompleteViewController.swift | 36 +++++++++---------- .../Complete/Components/CompletePage.swift | 23 +++--------- 5 files changed, 51 insertions(+), 40 deletions(-) diff --git a/dogether/Presentation/Base/BaseViewController.swift b/dogether/Presentation/Base/BaseViewController.swift index 8c1e2a16..94296d22 100644 --- a/dogether/Presentation/Base/BaseViewController.swift +++ b/dogether/Presentation/Base/BaseViewController.swift @@ -15,7 +15,7 @@ class BaseViewController: UIViewController, CoordinatorDelegate { var datas: (any BaseEntity)? var pages: Array? - private let disposeBag = DisposeBag() + let disposeBag = DisposeBag() override func viewDidLoad() { super.viewDidLoad() @@ -31,6 +31,7 @@ class BaseViewController: UIViewController, CoordinatorDelegate { configurePages(pages) setViewDatas() + bindAction() } /// 뷰의 시각적인 속성을 설정하는 역할을 합니다 @@ -63,6 +64,9 @@ class BaseViewController: UIViewController, CoordinatorDelegate { /// View를 구성하는 필수 데이터를 세팅하고 바인딩하는 역할을 합니다 func setViewDatas() { } + /// 버튼이나 제스처, Rx 이벤트 바인딩 + func bindAction() { } + /// ViewDatas의 변화에 Page가 update 되도록 바인딩하는 역할을 합니다 func bind(_ relay: BehaviorRelay) { relay diff --git a/dogether/Presentation/Common/DogetherButton.swift b/dogether/Presentation/Common/DogetherButton.swift index 579a32a7..d9b2acd7 100644 --- a/dogether/Presentation/Common/DogetherButton.swift +++ b/dogether/Presentation/Common/DogetherButton.swift @@ -7,9 +7,15 @@ import UIKit +import RxSwift +import RxCocoa + final class DogetherButton: BaseButton { private let title: String - + // ✅ 버튼 탭 이벤트 노출 (RxRelay) + let tapRelay = PublishRelay() + private let disposeBag = DisposeBag() + init(_ title: String) { self.title = title @@ -23,6 +29,11 @@ final class DogetherButton: BaseButton { setTitle(title, for: .normal) titleLabel?.font = Fonts.body1B layer.cornerRadius = 8 + + rx.tap + .throttle(.milliseconds(500), scheduler: MainScheduler.instance) // 중복클릭 방지 + .bind(to: tapRelay) + .disposed(by: disposeBag) } override func configureAction() { } diff --git a/dogether/Presentation/Common/JoinCodeShareButton.swift b/dogether/Presentation/Common/JoinCodeShareButton.swift index 73cfdb1e..5df464e0 100644 --- a/dogether/Presentation/Common/JoinCodeShareButton.swift +++ b/dogether/Presentation/Common/JoinCodeShareButton.swift @@ -7,6 +7,9 @@ import UIKit +import RxSwift +import RxCocoa + final class JoinCodeShareButton: BaseButton { private let codeLabel = UILabel() private let iconImageView = UIImageView( @@ -14,6 +17,10 @@ final class JoinCodeShareButton: BaseButton { ) private let stackView = UIStackView() + // ✅ 버튼 탭 이벤트 노출 + let tapRelay = PublishRelay() + private let disposeBag = DisposeBag() + override func configureView() { layer.cornerRadius = 12 backgroundColor = .grey700 @@ -26,6 +33,12 @@ final class JoinCodeShareButton: BaseButton { stackView.axis = .horizontal stackView.spacing = 8 stackView.isUserInteractionEnabled = false + + // ✅ 탭 이벤트 바인딩 + rx.tap + .throttle(.milliseconds(500), scheduler: MainScheduler.instance) // 중복클릭 방지 + .bind(to: tapRelay) + .disposed(by: disposeBag) } override func configureAction() { } diff --git a/dogether/Presentation/Features/Complete/CompleteViewController.swift b/dogether/Presentation/Features/Complete/CompleteViewController.swift index a59db885..2ef37c63 100644 --- a/dogether/Presentation/Features/Complete/CompleteViewController.swift +++ b/dogether/Presentation/Features/Complete/CompleteViewController.swift @@ -12,7 +12,6 @@ final class CompleteViewController: BaseViewController { private let viewModel = CompleteViewModel() override func viewDidLoad() { - completePage.delegate = self pages = [completePage] super.viewDidLoad() } @@ -24,25 +23,24 @@ final class CompleteViewController: BaseViewController { bind(viewModel.completeViewDatas) } -} - -protocol CompleteDelegate: AnyObject { - func goHomeAction() - func shareJoinCodeAction() -} - -extension CompleteViewController: CompleteDelegate { - func goHomeAction() { - coordinator?.setNavigationController(MainViewController()) - } - func shareJoinCodeAction() { - let data = viewModel.completeViewDatas.value + override func bindAction() { + completePage.homeTap + .emit(onNext: { [weak self] in + self?.coordinator?.setNavigationController(MainViewController()) + }) + .disposed(by: disposeBag) - let invite = SystemManager.inviteGroup( - groupName: data.groupInfo.name, - joinCode: data.joinCode - ) - present(UIActivityViewController(activityItems: invite, applicationActivities: nil), animated: true) + completePage.shareTap + .emit(onNext: { [weak self] in + guard let self else { return } + let data = viewModel.completeViewDatas.value + let invite = SystemManager.inviteGroup( + groupName: data.groupInfo.name, + joinCode: data.joinCode + ) + present(UIActivityViewController(activityItems: invite, applicationActivities: nil), animated: true) + }) + .disposed(by: disposeBag) } } diff --git a/dogether/Presentation/Features/Complete/Components/CompletePage.swift b/dogether/Presentation/Features/Complete/Components/CompletePage.swift index 476eaefc..ee32e09d 100644 --- a/dogether/Presentation/Features/Complete/Components/CompletePage.swift +++ b/dogether/Presentation/Features/Complete/Components/CompletePage.swift @@ -7,26 +7,11 @@ import UIKit +import RxCocoa + final class CompletePage: BasePage { - weak var delegate: CompleteDelegate? { - didSet { - completeButton.addAction( - UIAction { [weak self] _ in - guard let self else { return } - delegate?.goHomeAction() - }, - for: .touchUpInside - ) - - joinCodeShareButton.addAction( - UIAction { [weak self] _ in - guard let self else { return } - delegate?.shareJoinCodeAction() - }, - for: .touchUpInside - ) - } - } + var homeTap: Signal { completeButton.tapRelay.asSignal() } + var shareTap: Signal { joinCodeShareButton.tapRelay.asSignal() } private let firecrackerImageView = UIImageView(image: .firecracker) private let titleLabel = UILabel() From 3811d99f931ee2eb90d5292e6a36e8c8a6853338 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=8B=E1=85=B2=E1=84=8C=E1=85=A2=E1=84=92=E1=85=A9?= =?UTF-8?q?=E1=86=BC?= Date: Wed, 12 Nov 2025 14:43:33 +0900 Subject: [PATCH 2/4] =?UTF-8?q?refactor=20#128:=20delegate=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0=20=EB=B0=8F=20Relay/Signal=20=EA=B8=B0=EB=B0=98=20?= =?UTF-8?q?=EC=9D=B4=EB=B2=A4=ED=8A=B8=20=EC=A0=84=EB=8B=AC=20=EA=B5=AC?= =?UTF-8?q?=EC=A1=B0=EB=A1=9C=20=EB=B3=80=EA=B2=BD=20(2)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - TodoWritePage의 TodoWriteDelegate 제거 - UITextFieldDelegate 및 버튼 Action을 Rx 스트림으로 대체 - PublishRelay/Signal 기반 단방향 이벤트 플로우로 리팩토링 - ViewController에서 emit(onNext:)로 ViewModel 로직 연결 --- .../TodoWrite/Components/TodoWritePage.swift | 117 +++++++++++------- .../TodoWrite/TodoWriteViewController.swift | 76 +++++++----- 2 files changed, 112 insertions(+), 81 deletions(-) diff --git a/dogether/Presentation/Features/TodoWrite/Components/TodoWritePage.swift b/dogether/Presentation/Features/TodoWrite/Components/TodoWritePage.swift index 15f7a9cc..d30196fd 100644 --- a/dogether/Presentation/Features/TodoWrite/Components/TodoWritePage.swift +++ b/dogether/Presentation/Features/TodoWrite/Components/TodoWritePage.swift @@ -7,33 +7,24 @@ import UIKit +import RxSwift +import RxCocoa + final class TodoWritePage: BasePage { - var delegate: TodoWriteDelegate? { - didSet { - todoTextField.addAction( - UIAction { [weak self] _ in - guard let self else { return } - let todo = String((todoTextField.text ?? "").prefix(todoMaxLength)) - todoTextField.text = todo - delegate?.updateTodoAction(todo: todo) - }, for: .editingChanged - ) - - addButton.addAction( - UIAction { [weak self] _ in - guard let self else { return } - delegate?.addTodoAction(todoMaxCount: todoMaxCount) - }, for: .touchUpInside - ) - - saveButton.addAction( - UIAction { [weak self] _ in - guard let self else { return } - delegate?.saveTodoAction() - }, for: .touchUpInside - ) - } - } + // ✅ 외부 노출용 Signal + var todoChanged: Signal<(String?, Int)> { _todoChanged.asSignal() } + var addTap: Signal { _addTap.asSignal() } + var saveTap: Signal { _saveTap.asSignal() } + var removeTap: Signal { _removeTap.asSignal() } + var keyboardState: Signal { _keyboardState.asSignal() } + + // ✅ 내부 Relay + private let _todoChanged = PublishRelay<(String?, Int)>() + private let _addTap = PublishRelay() + private let _saveTap = PublishRelay() + private let _removeTap = PublishRelay() + private let _keyboardState = PublishRelay() + private let disposeBag = DisposeBag() private let navigationHeader = NavigationHeader(title: "투두 작성") @@ -137,15 +128,63 @@ final class TodoWritePage: BasePage { guard let self else { return } endEditing(true) } - - navigationHeader.delegate = coordinatorDelegate - todoTextField.delegate = self + navigationHeader.delegate = coordinatorDelegate todoTableView.delegate = self todoTableView.dataSource = self todoTableView.register(TodoWriteTableViewCell.self, forCellReuseIdentifier: TodoWriteTableViewCell.identifier) + // 텍스트 변경 → ViewModel로 전달 + 즉시 UI 갱신 + todoTextField.rx.text + .skip(1) + .do(onNext: { [weak self] _ in + guard let self else { return } + updateTextField() + }) + .map { ($0, self.todoMaxLength) } + .bind(to: _todoChanged) + .disposed(by: disposeBag) + + // 텍스트 길이 제한 (20자) + todoTextField.rx.controlEvent(.editingChanged) + .withLatestFrom(todoTextField.rx.text.orEmpty) + .subscribe(onNext: { [weak self] text in + guard let self else { return } + if text.count > self.todoMaxLength { + let limited = String(text.prefix(self.todoMaxLength)) + self.todoTextField.text = limited + self._todoChanged.accept((limited, self.todoMaxLength)) + } + }) + .disposed(by: disposeBag) + + addButton.rx.tap + .map { self.todoMaxCount } + .bind(to: _addTap) + .disposed(by: disposeBag) + + saveButton.tapRelay + .bind(to: _saveTap) + .disposed(by: disposeBag) + + // Return 키 입력 시 (textFieldShouldReturn 대체) + todoTextField.rx.controlEvent(.editingDidEndOnExit) + .map { self.todoMaxCount } + .bind(to: _addTap) + .disposed(by: disposeBag) + + // 키보드 등장 (textFieldDidBeginEditing 대체) + NotificationCenter.default.rx.notification(UIResponder.keyboardWillShowNotification) + .map { _ in true } + .bind(to: _keyboardState) + .disposed(by: disposeBag) + + // 키보드 사라짐 (textFieldDidEndEditing 대체) + NotificationCenter.default.rx.notification(UIResponder.keyboardWillHideNotification) + .map { _ in false } + .bind(to: _keyboardState) + .disposed(by: disposeBag) } override func configureHierarchy() { @@ -228,6 +267,7 @@ final class TodoWritePage: BasePage { currentTodos = datas.todos updateTodoLimitLabel() + updateTextField() emptyListView.isHidden = !datas.todos.isEmpty todoTableView.isHidden = datas.todos.isEmpty @@ -333,26 +373,9 @@ extension TodoWritePage: UITableViewDelegate, UITableViewDataSource { cell.setExtraInfo(todo: (currentTodos ?? [])[indexPath.row], index: indexPath.row) { [weak self] index in guard let self else { return } - delegate?.removeTodoAction(index: index) + _removeTap.accept(index) } return cell } } - -// MARK: - about keyboard (UITextFieldDelegate) -extension TodoWritePage: UITextFieldDelegate { - func textFieldShouldReturn(_ textField: UITextField) -> Bool { - guard addButton.isEnabled else { return false } - delegate?.addTodoAction(todoMaxCount: todoMaxCount) - return true - } - - func textFieldDidBeginEditing(_ textField: UITextField) { - delegate?.updateIsShowKeyboardAction(isShowKeyboard: true) - } - - func textFieldDidEndEditing(_ textField: UITextField) { - delegate?.updateIsShowKeyboardAction(isShowKeyboard: false) - } -} diff --git a/dogether/Presentation/Features/TodoWrite/TodoWriteViewController.swift b/dogether/Presentation/Features/TodoWrite/TodoWriteViewController.swift index e7cabc7f..a04bb8ed 100644 --- a/dogether/Presentation/Features/TodoWrite/TodoWriteViewController.swift +++ b/dogether/Presentation/Features/TodoWrite/TodoWriteViewController.swift @@ -15,8 +15,6 @@ final class TodoWriteViewController: BaseViewController { private let viewModel = TodoWriteViewModel() override func viewDidLoad() { - todoWritePage.delegate = self - pages = [todoWritePage] super.viewDidLoad() @@ -33,39 +31,49 @@ final class TodoWriteViewController: BaseViewController { bind(viewModel.todoWriteViewDatas) } -} - -// MARK: - delegate -protocol TodoWriteDelegate { - func updateIsShowKeyboardAction(isShowKeyboard: Bool) - func updateTodoAction(todo: String) - func addTodoAction(todoMaxCount: Int) - func removeTodoAction(index: Int) - func saveTodoAction() -} - -extension TodoWriteViewController: TodoWriteDelegate { - func updateIsShowKeyboardAction(isShowKeyboard: Bool) { - viewModel.updateIsShowKeyboard(isShowKeyboard: isShowKeyboard) - } - - func updateTodoAction(todo: String) { - viewModel.updateTodo(todo: todo) - } - - func addTodoAction(todoMaxCount: Int) { - viewModel.addTodo(todoMaxCount: todoMaxCount) - } - func removeTodoAction(index: Int) { - viewModel.removeTodo(index: index) - } - - func saveTodoAction() { - coordinator?.showPopup(self, type: .alert, alertType: .saveTodo) { [weak self] _ in - guard let self else { return } - trySaveTodo() - } + override func bindAction() { + todoWritePage.todoChanged + .emit(onNext: { [weak self] (text, maxLen) in + guard let self else { return } + viewModel.updateTodo(todo: text, todoMaxLength: maxLen) + }) + .disposed(by: disposeBag) + + todoWritePage.addTap + .emit(onNext: { [weak self] maxCount in + guard let self else { return } + viewModel.addTodo(todoMaxCount: maxCount) + }) + .disposed(by: disposeBag) + + todoWritePage.removeTap + .emit(onNext: { [weak self] index in + guard let self else { return } + viewModel.removeTodo(index: index) + }) + .disposed(by: disposeBag) + + todoWritePage.saveTap + .emit(onNext: { [weak self] in + guard let self else { return } + coordinator?.showPopup( + self, + type: .alert, + alertType: .saveTodo + ) { [weak self] _ in + self?.trySaveTodo() + } + }) + .disposed(by: disposeBag) + + + todoWritePage.keyboardState + .emit(onNext: { [weak self] isShow in + guard let self else { return } + viewModel.updateIsShowKeyboard(isShowKeyboard: isShow) + }) + .disposed(by: disposeBag) } } From c77e8212d7f90eb2f671754a711ed5237a7b52ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=8B=E1=85=B2=E1=84=8C=E1=85=A2=E1=84=92=E1=85=A9?= =?UTF-8?q?=E1=86=BC?= Date: Mon, 24 Nov 2025 21:53:07 +0900 Subject: [PATCH 3/4] =?UTF-8?q?refactor=20#128:=20=ED=94=BC=EB=93=9C?= =?UTF-8?q?=EB=B0=B1=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - BaseButton 공통 이벤트 처리 구조 적용 - Relay/Signal 기반 중간 View 이벤트 전달 구조 확장 --- dogether/Presentation/Base/BaseButton.swift | 22 +++++- .../Presentation/Common/DogetherButton.swift | 4 +- .../Common/JoinCodeShareButton.swift | 24 ++++--- .../Complete/Components/CompletePage.swift | 7 +- .../Main/Components/GroupInfoView.swift | 68 +++++++++++++++---- .../Features/Main/Components/MainPage.swift | 14 +++- .../Features/Main/MainViewController.swift | 36 ++++++++-- 7 files changed, 140 insertions(+), 35 deletions(-) diff --git a/dogether/Presentation/Base/BaseButton.swift b/dogether/Presentation/Base/BaseButton.swift index 91985efa..1b94e726 100644 --- a/dogether/Presentation/Base/BaseButton.swift +++ b/dogether/Presentation/Base/BaseButton.swift @@ -6,8 +6,18 @@ // import UIKit +import RxSwift +import RxCocoa class BaseButton: UIButton { + /// 외부 노출용 Signal + var tap: Signal { tapRelay.asSignal() } + + /// 내부 이벤트 스트림 +// fileprivate let tapRelay = PublishRelay() + let tapRelay = PublishRelay() + fileprivate let disposeBag = DisposeBag() + override init(frame: CGRect) { super.init(frame: frame) @@ -22,7 +32,9 @@ class BaseButton: UIButton { func configureView() { } /// 뷰의 동작 및 이벤트 처리를 설정하는 역할을 합니다 - func configureAction() { } + func configureAction() { + bindTap() + } /// 뷰 계층을 구성하는 역할을 합니다 func configureHierarchy() { } @@ -32,4 +44,12 @@ class BaseButton: UIButton { /// 뷰의 가변 요소들을 업데이트하는 역할을 합니다 func updateView(_ data: any BaseEntity) { } + + /// 공통 버튼 탭 이벤트 바인딩 + private func bindTap() { + rx.tap + .throttle(.milliseconds(500), scheduler: MainScheduler.instance) + .bind(to: tapRelay) + .disposed(by: disposeBag) + } } diff --git a/dogether/Presentation/Common/DogetherButton.swift b/dogether/Presentation/Common/DogetherButton.swift index d9b2acd7..07f127c2 100644 --- a/dogether/Presentation/Common/DogetherButton.swift +++ b/dogether/Presentation/Common/DogetherButton.swift @@ -36,7 +36,9 @@ final class DogetherButton: BaseButton { .disposed(by: disposeBag) } - override func configureAction() { } + override func configureAction() { + super.configureAction() + } override func configureHierarchy() { } diff --git a/dogether/Presentation/Common/JoinCodeShareButton.swift b/dogether/Presentation/Common/JoinCodeShareButton.swift index 5df464e0..3098d12f 100644 --- a/dogether/Presentation/Common/JoinCodeShareButton.swift +++ b/dogether/Presentation/Common/JoinCodeShareButton.swift @@ -7,8 +7,8 @@ import UIKit -import RxSwift -import RxCocoa +//import RxSwift +//import RxCocoa final class JoinCodeShareButton: BaseButton { private let codeLabel = UILabel() @@ -17,9 +17,9 @@ final class JoinCodeShareButton: BaseButton { ) private let stackView = UIStackView() - // ✅ 버튼 탭 이벤트 노출 - let tapRelay = PublishRelay() - private let disposeBag = DisposeBag() +// // ✅ 버튼 탭 이벤트 노출 +// let tapRelay = PublishRelay() +// private let disposeBag = DisposeBag() override func configureView() { layer.cornerRadius = 12 @@ -34,14 +34,16 @@ final class JoinCodeShareButton: BaseButton { stackView.spacing = 8 stackView.isUserInteractionEnabled = false - // ✅ 탭 이벤트 바인딩 - rx.tap - .throttle(.milliseconds(500), scheduler: MainScheduler.instance) // 중복클릭 방지 - .bind(to: tapRelay) - .disposed(by: disposeBag) +// // ✅ 탭 이벤트 바인딩 +// rx.tap +// .throttle(.milliseconds(500), scheduler: MainScheduler.instance) // 중복클릭 방지 +// .bind(to: tapRelay) +// .disposed(by: disposeBag) } - override func configureAction() { } + override func configureAction() { + super.configureAction() + } override func configureHierarchy() { addSubview(stackView) diff --git a/dogether/Presentation/Features/Complete/Components/CompletePage.swift b/dogether/Presentation/Features/Complete/Components/CompletePage.swift index ee32e09d..5c427234 100644 --- a/dogether/Presentation/Features/Complete/Components/CompletePage.swift +++ b/dogether/Presentation/Features/Complete/Components/CompletePage.swift @@ -10,8 +10,11 @@ import UIKit import RxCocoa final class CompletePage: BasePage { - var homeTap: Signal { completeButton.tapRelay.asSignal() } - var shareTap: Signal { joinCodeShareButton.tapRelay.asSignal() } +// var homeTap: Signal { completeButton.tapRelay.asSignal() } +// var shareTap: Signal { joinCodeShareButton.tapRelay.asSignal() } + + var homeTap: Signal { completeButton.tap } + var shareTap: Signal { joinCodeShareButton.tap } private let firecrackerImageView = UIImageView(image: .firecracker) private let titleLabel = UILabel() diff --git a/dogether/Presentation/Features/Main/Components/GroupInfoView.swift b/dogether/Presentation/Features/Main/Components/GroupInfoView.swift index 4732e13e..650107c3 100644 --- a/dogether/Presentation/Features/Main/Components/GroupInfoView.swift +++ b/dogether/Presentation/Features/Main/Components/GroupInfoView.swift @@ -7,20 +7,40 @@ import UIKit -final class GroupInfoView: BaseView { - var delegate: MainDelegate? { - didSet { - groupNameStackView.addTapAction { [weak self] _ in - guard let self else { return } - delegate?.updateBottomSheetVisibleAction(isShowSheet: true) - } - - joinCodeStackView.addTapAction { [weak self] _ in - guard let self else { return } - delegate?.inviteAction() - } - } +import RxSwift +import RxRelay +import RxCocoa + +struct GroupInfoEvent { + enum Action { + case openGroupSelector + case invite } + + let action: Action + let group: ChallengeGroupInfo +} + +final class GroupInfoView: BaseView { +// var delegate: MainDelegate? { +// didSet { +// groupNameStackView.addTapAction { [weak self] in +// guard let self else { return } +// delegate?.updateBottomSheetVisibleAction(isShowSheet: true) +// } +// +// joinCodeStackView.addTapAction { [weak self] in +// guard let self else { return } +// delegate?.inviteAction() +// } +// } +// } + + // ✅ 외부 노출용 이벤트 스트림 + var event: Signal { _eventRelay.asSignal() } + + private let _eventRelay = PublishRelay() + private let disposeBag = DisposeBag() private let hasCopyImage: Bool private(set) var challengeGroupInfo: ChallengeGroupInfo @@ -128,7 +148,27 @@ final class GroupInfoView: BaseView { durationProgressView.transform = CGAffineTransform(translationX: 0, y: 1) // MARK: 디자인 디테일 반영 } - override func configureAction() { } + override func configureAction() { + groupNameStackView.addTapAction { [weak self] in + guard let self else { return } + _eventRelay.accept( + GroupInfoEvent( + action: .openGroupSelector, + group: challengeGroupInfo + ) + ) + } + + joinCodeStackView.addTapAction { [weak self] in + guard let self else { return } + _eventRelay.accept( + GroupInfoEvent( + action: .invite, + group: challengeGroupInfo + ) + ) + } + } override func configureHierarchy() { [nameLabel, changeGroupImageView, nameSpacerView].forEach { groupNameStackView.addArrangedSubview($0) } diff --git a/dogether/Presentation/Features/Main/Components/MainPage.swift b/dogether/Presentation/Features/Main/Components/MainPage.swift index 89d90120..16c1ae65 100644 --- a/dogether/Presentation/Features/Main/Components/MainPage.swift +++ b/dogether/Presentation/Features/Main/Components/MainPage.swift @@ -8,12 +8,16 @@ import UIKit import SnapKit +import RxSwift +import RxRelay +import RxCocoa + final class MainPage: BasePage { var delegate: MainDelegate? { didSet { bottomSheetView.delegate = delegate - groupInfoView.delegate = delegate +// groupInfoView.delegate = delegate rankingButton.delegate = delegate sheetHeaderView.delegate = delegate @@ -23,6 +27,10 @@ final class MainPage: BasePage { } } + var groupInfoEvent: Signal { _groupInfoEvent.asSignal() } + private let _groupInfoEvent = PublishRelay() + private let disposeBag = DisposeBag() + private let dogetherHeader = DogetherHeader() private let dosikCommentButton = DosikCommentButton() @@ -60,6 +68,10 @@ final class MainPage: BasePage { let dogetherPanGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePanGesture(_:))) dogetherPanGesture.delegate = self dogetherSheet.addGestureRecognizer(dogetherPanGesture) + + groupInfoView.event + .emit(to: _groupInfoEvent) + .disposed(by: disposeBag) } override func configureHierarchy() { diff --git a/dogether/Presentation/Features/Main/MainViewController.swift b/dogether/Presentation/Features/Main/MainViewController.swift index b739221b..19067392 100644 --- a/dogether/Presentation/Features/Main/MainViewController.swift +++ b/dogether/Presentation/Features/Main/MainViewController.swift @@ -33,6 +33,32 @@ final class MainViewController: BaseViewController { bind(viewModel.sheetViewDatas) bind(viewModel.timerViewDatas) } + + override func bindAction() { + mainPage.groupInfoEvent + .emit(onNext: { [weak self] event in + guard let self else { return } + + switch event.action { + + case .openGroupSelector: + viewModel.bottomSheetViewDatas.update { + $0.isShowSheet = true + } + + case .invite: + let inviteGroup = SystemManager.inviteGroup( + groupName: event.group.name, + joinCode: event.group.joinCode + ) + present( + UIActivityViewController(activityItems: inviteGroup, applicationActivities: nil), + animated: true + ) + } + }) + .disposed(by: disposeBag) + } } extension MainViewController { @@ -114,7 +140,7 @@ protocol MainDelegate { func updateBottomSheetVisibleAction(isShowSheet: Bool) func selectGroupAction(index: Int) func addGroupAction() - func inviteAction() +// func inviteAction() func goPastAction() func goFutureAction() func startTimerAction() @@ -186,10 +212,10 @@ extension MainViewController: MainDelegate { coordinator?.pushViewController(startViewController, datas: startViewDatas) } - func inviteAction() { - let inviteGroup = SystemManager.inviteGroup(groupName: viewModel.currentGroup.name, joinCode: viewModel.currentGroup.joinCode) - present(UIActivityViewController(activityItems: inviteGroup, applicationActivities: nil), animated: true) - } +// func inviteAction() { +// let inviteGroup = SystemManager.inviteGroup(groupName: viewModel.currentGroup.name, joinCode: viewModel.currentGroup.joinCode) +// present(UIActivityViewController(activityItems: inviteGroup, applicationActivities: nil), animated: true) +// } func goPastAction() { viewModel.sheetViewDatas.update { From 7893489f370115e8c4cd7f002ac21a6d789ecb13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=8B=E1=85=B2=E1=84=8C=E1=85=A2=E1=84=92=E1=85=A9?= =?UTF-8?q?=E1=86=BC?= Date: Thu, 27 Nov 2025 15:25:31 +0900 Subject: [PATCH 4/4] =?UTF-8?q?chore=20#128:=20=EB=A6=AC=EB=B2=A0=EC=9D=B4?= =?UTF-8?q?=EC=8A=A4=20=EB=B0=8F=20=EC=BD=94=EB=93=9C=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dogether/Presentation/Base/BaseButton.swift | 3 +-- dogether/Presentation/Common/DogetherButton.swift | 8 -------- .../Presentation/Common/JoinCodeShareButton.swift | 13 ------------- .../Complete/Components/CompletePage.swift | 3 --- .../Features/Main/Components/GroupInfoView.swift | 4 ++-- .../TodoWrite/Components/TodoWritePage.swift | 14 ++++---------- .../TodoWrite/TodoWriteViewController.swift | 2 +- 7 files changed, 8 insertions(+), 39 deletions(-) diff --git a/dogether/Presentation/Base/BaseButton.swift b/dogether/Presentation/Base/BaseButton.swift index 1b94e726..5135dbdd 100644 --- a/dogether/Presentation/Base/BaseButton.swift +++ b/dogether/Presentation/Base/BaseButton.swift @@ -12,10 +12,9 @@ import RxCocoa class BaseButton: UIButton { /// 외부 노출용 Signal var tap: Signal { tapRelay.asSignal() } - /// 내부 이벤트 스트림 -// fileprivate let tapRelay = PublishRelay() let tapRelay = PublishRelay() + fileprivate let disposeBag = DisposeBag() override init(frame: CGRect) { diff --git a/dogether/Presentation/Common/DogetherButton.swift b/dogether/Presentation/Common/DogetherButton.swift index 07f127c2..f63cfcac 100644 --- a/dogether/Presentation/Common/DogetherButton.swift +++ b/dogether/Presentation/Common/DogetherButton.swift @@ -12,9 +12,6 @@ import RxCocoa final class DogetherButton: BaseButton { private let title: String - // ✅ 버튼 탭 이벤트 노출 (RxRelay) - let tapRelay = PublishRelay() - private let disposeBag = DisposeBag() init(_ title: String) { self.title = title @@ -29,11 +26,6 @@ final class DogetherButton: BaseButton { setTitle(title, for: .normal) titleLabel?.font = Fonts.body1B layer.cornerRadius = 8 - - rx.tap - .throttle(.milliseconds(500), scheduler: MainScheduler.instance) // 중복클릭 방지 - .bind(to: tapRelay) - .disposed(by: disposeBag) } override func configureAction() { diff --git a/dogether/Presentation/Common/JoinCodeShareButton.swift b/dogether/Presentation/Common/JoinCodeShareButton.swift index 3098d12f..0057748a 100644 --- a/dogether/Presentation/Common/JoinCodeShareButton.swift +++ b/dogether/Presentation/Common/JoinCodeShareButton.swift @@ -7,9 +7,6 @@ import UIKit -//import RxSwift -//import RxCocoa - final class JoinCodeShareButton: BaseButton { private let codeLabel = UILabel() private let iconImageView = UIImageView( @@ -17,10 +14,6 @@ final class JoinCodeShareButton: BaseButton { ) private let stackView = UIStackView() -// // ✅ 버튼 탭 이벤트 노출 -// let tapRelay = PublishRelay() -// private let disposeBag = DisposeBag() - override func configureView() { layer.cornerRadius = 12 backgroundColor = .grey700 @@ -33,12 +26,6 @@ final class JoinCodeShareButton: BaseButton { stackView.axis = .horizontal stackView.spacing = 8 stackView.isUserInteractionEnabled = false - -// // ✅ 탭 이벤트 바인딩 -// rx.tap -// .throttle(.milliseconds(500), scheduler: MainScheduler.instance) // 중복클릭 방지 -// .bind(to: tapRelay) -// .disposed(by: disposeBag) } override func configureAction() { diff --git a/dogether/Presentation/Features/Complete/Components/CompletePage.swift b/dogether/Presentation/Features/Complete/Components/CompletePage.swift index 5c427234..6a9e00a8 100644 --- a/dogether/Presentation/Features/Complete/Components/CompletePage.swift +++ b/dogether/Presentation/Features/Complete/Components/CompletePage.swift @@ -10,9 +10,6 @@ import UIKit import RxCocoa final class CompletePage: BasePage { -// var homeTap: Signal { completeButton.tapRelay.asSignal() } -// var shareTap: Signal { joinCodeShareButton.tapRelay.asSignal() } - var homeTap: Signal { completeButton.tap } var shareTap: Signal { joinCodeShareButton.tap } diff --git a/dogether/Presentation/Features/Main/Components/GroupInfoView.swift b/dogether/Presentation/Features/Main/Components/GroupInfoView.swift index 650107c3..b49ea074 100644 --- a/dogether/Presentation/Features/Main/Components/GroupInfoView.swift +++ b/dogether/Presentation/Features/Main/Components/GroupInfoView.swift @@ -149,7 +149,7 @@ final class GroupInfoView: BaseView { } override func configureAction() { - groupNameStackView.addTapAction { [weak self] in + groupNameStackView.addTapAction { [weak self] _ in guard let self else { return } _eventRelay.accept( GroupInfoEvent( @@ -159,7 +159,7 @@ final class GroupInfoView: BaseView { ) } - joinCodeStackView.addTapAction { [weak self] in + joinCodeStackView.addTapAction { [weak self] _ in guard let self else { return } _eventRelay.accept( GroupInfoEvent( diff --git a/dogether/Presentation/Features/TodoWrite/Components/TodoWritePage.swift b/dogether/Presentation/Features/TodoWrite/Components/TodoWritePage.swift index d30196fd..3b028263 100644 --- a/dogether/Presentation/Features/TodoWrite/Components/TodoWritePage.swift +++ b/dogether/Presentation/Features/TodoWrite/Components/TodoWritePage.swift @@ -11,15 +11,13 @@ import RxSwift import RxCocoa final class TodoWritePage: BasePage { - // ✅ 외부 노출용 Signal - var todoChanged: Signal<(String?, Int)> { _todoChanged.asSignal() } + var todoChanged: Signal<(String, Int)> { _todoChanged.asSignal() } var addTap: Signal { _addTap.asSignal() } var saveTap: Signal { _saveTap.asSignal() } var removeTap: Signal { _removeTap.asSignal() } var keyboardState: Signal { _keyboardState.asSignal() } - // ✅ 내부 Relay - private let _todoChanged = PublishRelay<(String?, Int)>() + private let _todoChanged = PublishRelay<(String, Int)>() private let _addTap = PublishRelay() private let _saveTap = PublishRelay() private let _removeTap = PublishRelay() @@ -135,8 +133,8 @@ final class TodoWritePage: BasePage { todoTableView.dataSource = self todoTableView.register(TodoWriteTableViewCell.self, forCellReuseIdentifier: TodoWriteTableViewCell.identifier) - // 텍스트 변경 → ViewModel로 전달 + 즉시 UI 갱신 todoTextField.rx.text + .orEmpty .skip(1) .do(onNext: { [weak self] _ in guard let self else { return } @@ -146,7 +144,6 @@ final class TodoWritePage: BasePage { .bind(to: _todoChanged) .disposed(by: disposeBag) - // 텍스트 길이 제한 (20자) todoTextField.rx.controlEvent(.editingChanged) .withLatestFrom(todoTextField.rx.text.orEmpty) .subscribe(onNext: { [weak self] text in @@ -168,19 +165,16 @@ final class TodoWritePage: BasePage { .bind(to: _saveTap) .disposed(by: disposeBag) - // Return 키 입력 시 (textFieldShouldReturn 대체) todoTextField.rx.controlEvent(.editingDidEndOnExit) .map { self.todoMaxCount } .bind(to: _addTap) .disposed(by: disposeBag) - - // 키보드 등장 (textFieldDidBeginEditing 대체) + NotificationCenter.default.rx.notification(UIResponder.keyboardWillShowNotification) .map { _ in true } .bind(to: _keyboardState) .disposed(by: disposeBag) - // 키보드 사라짐 (textFieldDidEndEditing 대체) NotificationCenter.default.rx.notification(UIResponder.keyboardWillHideNotification) .map { _ in false } .bind(to: _keyboardState) diff --git a/dogether/Presentation/Features/TodoWrite/TodoWriteViewController.swift b/dogether/Presentation/Features/TodoWrite/TodoWriteViewController.swift index a04bb8ed..e254ad73 100644 --- a/dogether/Presentation/Features/TodoWrite/TodoWriteViewController.swift +++ b/dogether/Presentation/Features/TodoWrite/TodoWriteViewController.swift @@ -36,7 +36,7 @@ final class TodoWriteViewController: BaseViewController { todoWritePage.todoChanged .emit(onNext: { [weak self] (text, maxLen) in guard let self else { return } - viewModel.updateTodo(todo: text, todoMaxLength: maxLen) + viewModel.updateTodo(todo: text) }) .disposed(by: disposeBag)