-
Notifications
You must be signed in to change notification settings - Fork 15
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Feat] #392 - 솝마디 결과 뷰, 부적 화면 전환 및 데이터 바인딩 #394
Changes from 11 commits
5f1e46a
7d710c4
e2a0dab
3740243
acd62e1
d057980
214dd4b
4c4260a
b76e5e7
17223ed
58d6cea
0f490eb
f722877
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
// | ||
// DailySoptuneCardPresentable.swift | ||
// DailySoptuneFeatureInterface | ||
// | ||
// Created by Jae Hyun Lee on 9/28/24. | ||
// Copyright © 2024 SOPT-iOS. All rights reserved. | ||
// | ||
|
||
import BaseFeatureDependency | ||
import Core | ||
|
||
public protocol DailySoptuneCardViewControllable: ViewControllable { } | ||
|
||
public protocol DailySoptuneCardCoordinatable { | ||
var onGoToHomeButtonTapped: (() -> Void)? { get set } | ||
var onBackButtonTapped: (() -> Void)? { get set } | ||
} | ||
|
||
public typealias DailySoptuneCardViewModelType = ViewModelType & DailySoptuneCardCoordinatable | ||
public typealias DailySoptuneCardPresentable = (vc: DailySoptuneCardViewControllable, vm: any DailySoptuneCardViewModelType) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
// | ||
// DailySoptuneCardCoordinator.swift | ||
// DailySoptuneFeature | ||
// | ||
// Created by Jae Hyun Lee on 9/28/24. | ||
// Copyright © 2024 SOPT-iOS. All rights reserved. | ||
// | ||
|
||
import UIKit | ||
import Combine | ||
|
||
import Core | ||
import BaseFeatureDependency | ||
import DailySoptuneFeatureInterface | ||
import Domain | ||
|
||
public final class DailySoptuneCardCoordinator: DefaultCoordinator { | ||
|
||
public var requestCoordinating: (() -> Void)? | ||
public var finishFlow: (() -> Void)? | ||
|
||
private let factory: DailySoptuneFeatureBuildable | ||
private let router: Router | ||
|
||
private let cardModel: DailySoptuneCardModel | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 질문! public override func start(cardModel: DailySoptuneCardModel) {
showDailySoptuneCard(cardModel: DailySoptuneCardModel)
} There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 헉 ... 이 부분 논의해보고 수정하겠습니다 ...ㅇ.ㅇ |
||
|
||
private weak var rootController: UINavigationController? | ||
|
||
public init(router: Router, factory: DailySoptuneFeatureBuildable, cardModel: DailySoptuneCardModel) { | ||
self.router = router | ||
self.factory = factory | ||
self.cardModel = cardModel | ||
} | ||
|
||
public override func start() { | ||
showDailySoptuneCard() | ||
} | ||
|
||
private func showDailySoptuneCard() { | ||
var dailySoptuneCard = factory.makeDailySoptuneCardVC(cardModel: cardModel) | ||
|
||
dailySoptuneCard.vm.onBackButtonTapped = { [weak self] in | ||
self?.router.dismissModule(animated: true) | ||
self?.finishFlow?() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. guard let으로 self 한 번 확인해주세요 ! |
||
} | ||
|
||
dailySoptuneCard.vm.onGoToHomeButtonTapped = { [weak self] in | ||
self?.requestCoordinating?() | ||
} | ||
|
||
self.rootController = dailySoptuneCard.vc.asNavigationController | ||
self.router.present(self.rootController, animated: true, modalPresentationSytle: .overFullScreen) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,5 @@ | ||
// | ||
// DailySoptuneCoordinator.swift | ||
// DailySoptuneResultCoordinator.swift | ||
// DailySoptuneFeature | ||
// | ||
// Created by Jae Hyun Lee on 9/21/24. | ||
|
@@ -15,13 +15,16 @@ import DailySoptuneFeatureInterface | |
import Domain | ||
import PokeFeatureInterface | ||
|
||
public final class DailySoptuneCoordinator: DefaultCoordinator { | ||
public final class DailySoptuneResultCoordinator: DefaultCoordinator { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ㅌㅋㅋㅋㅋ죄송합니다 .. 🫶 |
||
|
||
public var requestCoordinating: (() -> Void)? | ||
public var finishFlow: (() -> Void)? | ||
|
||
private let factory: DailySoptuneFeatureBuildable | ||
private let pokeFactory: PokeFeatureBuildable | ||
private let router: Router | ||
|
||
private weak var rootController: UINavigationController? | ||
|
||
public init(router: Router, factory: DailySoptuneFeatureBuildable, pokeFactory: PokeFeatureBuildable) { | ||
self.router = router | ||
self.factory = factory | ||
|
@@ -52,12 +55,44 @@ public final class DailySoptuneCoordinator: DefaultCoordinator { | |
private func showDailySoptuneResult(resultModel: DailySoptuneResultModel) { | ||
var dailySoptuneResult = factory.makeDailySoptuneResultVC(resultModel: resultModel) | ||
|
||
dailySoptuneResult.vm.onNaviBackButtonTapped = { [weak self] in | ||
self?.router.dismissModule(animated: true) | ||
self?.finishFlow?() | ||
} | ||
|
||
dailySoptuneResult.vm.onKokButtonTapped = { [weak self] userModel in | ||
guard let self else { return .empty() } | ||
return self.showMessageBottomSheet(userModel: userModel, on: dailySoptuneResult.vc.viewController) | ||
} | ||
|
||
router.push(dailySoptuneResult.vc) | ||
dailySoptuneResult.vm.onReceiveTodaysFortuneCardButtonTapped = { [weak self] cardModel in | ||
guard let self else { return } | ||
self.runDailySoptuneCardFlow(cardModel: cardModel) | ||
} | ||
|
||
rootController = dailySoptuneResult.vc.asNavigationController | ||
router.present(rootController, animated: false, modalPresentationSytle: .overFullScreen) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이 부분 저는 viewcontrollable까지만 전달했는데 asNavigationController로 지정해주신 이유가 있을까요? 근데 vc를 넘겨주는 것과 vc.viewControllable의 차이를 잘 모르겠어요. . . @lsj8706 혹시 설명해주실 수 있나요 ? 선배님 .. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 아하!!! 감사합니다 |
||
} | ||
|
||
internal func runDailySoptuneCardFlow(cardModel: DailySoptuneCardModel) { | ||
let dailySoptuneCardCoordinator = DailySoptuneCardCoordinator( | ||
router: Router( | ||
rootController: rootController ?? self.router.asNavigationController | ||
), factory: factory | ||
, cardModel: cardModel | ||
) | ||
|
||
dailySoptuneCardCoordinator.finishFlow = { [weak self, weak dailySoptuneCardCoordinator] in | ||
dailySoptuneCardCoordinator?.childCoordinators = [] | ||
self?.removeDependency(dailySoptuneCardCoordinator) | ||
} | ||
|
||
dailySoptuneCardCoordinator.requestCoordinating = { [weak self] in | ||
self?.requestCoordinating?() | ||
} | ||
|
||
addDependency(dailySoptuneCardCoordinator) | ||
dailySoptuneCardCoordinator.start() | ||
} | ||
|
||
private func showMessageBottomSheet(userModel: PokeUserModel, on view: UIViewController?) -> AnyPublisher<(PokeUserModel, PokeMessageModel, isAnonymous: Bool), Never> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이건 그냥 제가 예전에 쌓아둔 개인적인 아쉬움인데 공유차 남겨요. 현재는 바텀 시트를 보여주고 이 바텀시트에서 사용자가 선택한 데이터들을 Publisher 형태로 제공하고 있는데 이걸 바텀 시트를 띄우는 메서드인 그래서 이런 바텀 시트류의 UI에서는 콜백 인터페이스가 Publisher나 클로저가 아닌 Delegate 패턴이어야 더 명확하다고 생각해요. (예전에는 이런 개념이 없었어서 그냥 혼용했었음 😅) private func showMessageBottomSheet(userModel: PokeUserModel, on view: UIViewController?) {
guard let bottomSheet = self.pokeFactory
.makePokeMessageTemplateBottomSheet(messageType: messageType)
// 생략
bottomSheet.delegate = self
// 생략
}
extension DailySoptuneResultCoordinator: MessageBottomSheetDelegate {
func messageBottomSheet(pickedMessage: PokeMessageModel) {} // 필요한 정보들 전달(축약 버전)
} 즉, 이벤트 전달을 위한 바인딩을 할 때 언제 클로저(또는 Publisher)를 쓸지 또는 Delegate를 쓰면 좋을지 상황에 맞게 잘 고민해보면 좋을 것 같아요. 각각 장단점이 있어요. 사실 여기서는 이미 publisher로 인터페이스가 맞춰져 있어서 그냥 이대로 가는게 최선이에요 ㅋㅋㅋ 그냥 공부할 때 도움되라고 남긴 코멘트입니당 |
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,11 +7,21 @@ | |
// | ||
|
||
import UIKit | ||
import Combine | ||
|
||
import Core | ||
import DSKit | ||
import Domain | ||
|
||
public final class DailySoptuneCardVC: UIViewController { | ||
import BaseFeatureDependency | ||
|
||
public final class DailySoptuneCardVC: UIViewController, DailySoptuneCardViewControllable { | ||
|
||
// MARK: - Properties | ||
|
||
public var viewModel: DailySoptuneCardViewModel | ||
private let cardModel: DailySoptuneCardModel | ||
private var cancelBag = CancelBag() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 요거 |
||
|
||
// MARK: - UI Components | ||
|
||
|
@@ -22,11 +32,9 @@ public final class DailySoptuneCardVC: UIViewController { | |
private let subCardLabel = UILabel().then { | ||
$0.textColor = DSKitAsset.Colors.gray300.color | ||
$0.font = DSKitFontFamily.Suit.semiBold.font(size: 16) | ||
$0.text = "어려움을 전부 극복할" | ||
} | ||
|
||
private let cardLabel = UILabel().then { | ||
$0.text = "OO부적이 왔솝" | ||
$0.textColor = DSKitAsset.Colors.white100.color | ||
$0.font = DSKitFontFamily.Suit.bold.font(size: 28) | ||
} | ||
|
@@ -36,12 +44,27 @@ public final class DailySoptuneCardVC: UIViewController { | |
} | ||
|
||
private let goToHomeButton = AppOutlinedButton(title: I18N.DailySoptune.goHome) | ||
|
||
// MARK: - initialization | ||
|
||
init(cardModel: DailySoptuneCardModel, viewModel: DailySoptuneCardViewModel) { | ||
self.cardModel = cardModel | ||
self.viewModel = viewModel | ||
super.init(nibName: nil, bundle: nil) | ||
} | ||
|
||
required init?(coder: NSCoder) { | ||
fatalError("init(coder:) has not been implemented") | ||
} | ||
|
||
// MARK: - View Life Cycle | ||
|
||
public override func viewDidLoad() { | ||
super.viewDidLoad() | ||
|
||
setUI() | ||
setLayout() | ||
setData() | ||
bindViewModels() | ||
} | ||
} | ||
|
||
|
@@ -86,3 +109,23 @@ private extension DailySoptuneCardVC { | |
} | ||
} | ||
} | ||
|
||
// MARK: - Methods | ||
|
||
private extension DailySoptuneCardVC { | ||
func setData() { | ||
self.subCardLabel.text = cardModel.description | ||
self.cardLabel.text = "\(cardModel.name)이 왔솝" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 아 ㅋㅋㅋ 왔솝이 의도된 워딩인거죠???ㅋㅋㅋㅋ |
||
self.cardLabel.partColorChange(targetString: "\(cardModel.name)", textColor: UIColor(hex: "\(cardModel.imageColorCode)")) | ||
self.cardImage.setImage(with: cardModel.imageURL) | ||
} | ||
|
||
private func bindViewModels() { | ||
let input = DailySoptuneCardViewModel.Input( | ||
goToHomeButtonTap: self.goToHomeButton.publisher(for: .touchUpInside).mapVoid().asDriver(), | ||
backButtonTap: self.backButton.publisher(for: .touchUpInside).mapVoid().asDriver() | ||
) | ||
|
||
let output = self.viewModel.transform(from: input, cancelBag: self.cancelBag) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,67 @@ | ||||||||||||||||||||||
// | ||||||||||||||||||||||
// DailySoptuneCardViewModel.swift | ||||||||||||||||||||||
// DailySoptuneFeature | ||||||||||||||||||||||
// | ||||||||||||||||||||||
// Created by Jae Hyun Lee on 9/28/24. | ||||||||||||||||||||||
// Copyright © 2024 SOPT-iOS. All rights reserved. | ||||||||||||||||||||||
// | ||||||||||||||||||||||
|
||||||||||||||||||||||
import Foundation | ||||||||||||||||||||||
import Combine | ||||||||||||||||||||||
|
||||||||||||||||||||||
import Core | ||||||||||||||||||||||
import Domain | ||||||||||||||||||||||
|
||||||||||||||||||||||
import DailySoptuneFeatureInterface | ||||||||||||||||||||||
|
||||||||||||||||||||||
public final class DailySoptuneCardViewModel: DailySoptuneCardViewModelType { | ||||||||||||||||||||||
|
||||||||||||||||||||||
public var onGoToHomeButtonTapped: (() -> Void)? | ||||||||||||||||||||||
public var onBackButtonTapped: (() -> Void)? | ||||||||||||||||||||||
|
||||||||||||||||||||||
// MARK: - Properties | ||||||||||||||||||||||
|
||||||||||||||||||||||
private let useCase: DailySoptuneUseCase | ||||||||||||||||||||||
private var cancelBag = CancelBag() | ||||||||||||||||||||||
|
||||||||||||||||||||||
// MARK: - Inputs | ||||||||||||||||||||||
|
||||||||||||||||||||||
public struct Input { | ||||||||||||||||||||||
let goToHomeButtonTap: Driver<Void> | ||||||||||||||||||||||
let backButtonTap: Driver<Void> | ||||||||||||||||||||||
} | ||||||||||||||||||||||
|
||||||||||||||||||||||
// MARK: - Outpust | ||||||||||||||||||||||
|
||||||||||||||||||||||
public struct Output { | ||||||||||||||||||||||
|
||||||||||||||||||||||
} | ||||||||||||||||||||||
|
||||||||||||||||||||||
// MARK: - initialization | ||||||||||||||||||||||
|
||||||||||||||||||||||
public init(useCase: DailySoptuneUseCase) { | ||||||||||||||||||||||
self.useCase = useCase | ||||||||||||||||||||||
} | ||||||||||||||||||||||
} | ||||||||||||||||||||||
|
||||||||||||||||||||||
extension DailySoptuneCardViewModel { | ||||||||||||||||||||||
|
||||||||||||||||||||||
public func transform(from input: Input, cancelBag: CancelBag) -> Output { | ||||||||||||||||||||||
let output = Output() | ||||||||||||||||||||||
|
||||||||||||||||||||||
input.goToHomeButtonTap | ||||||||||||||||||||||
.withUnretained(self) | ||||||||||||||||||||||
.sink { _ in | ||||||||||||||||||||||
self.onGoToHomeButtonTapped?() | ||||||||||||||||||||||
}.store(in: cancelBag) | ||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이렇게 쓰면 withUnretained는 의미가 없을거에요.
Suggested change
|
||||||||||||||||||||||
|
||||||||||||||||||||||
input.backButtonTap | ||||||||||||||||||||||
.withUnretained(self) | ||||||||||||||||||||||
.sink { _ in | ||||||||||||||||||||||
self.onBackButtonTapped?() | ||||||||||||||||||||||
}.store(in: cancelBag) | ||||||||||||||||||||||
|
||||||||||||||||||||||
return output | ||||||||||||||||||||||
} | ||||||||||||||||||||||
|
||||||||||||||||||||||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍👍