Skip to content
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] #467 - 회원가입 UI 구현 #474

Open
wants to merge 11 commits into
base: feat/#465-signup
Choose a base branch
from

Conversation

meltsplit
Copy link
Contributor

🌴 PR 요약

🌱 작업한 브랜치

🌱 PR Point

📌 참고 사항

  • 로그인 -> 회원가입뷰로 이동할 때 transition 단계에서 레이아웃이 깨지는 현상이 있습니다.
    • 작업량이 많아 다른 뷰작업 모두 끝낸 후에 해당 오류 해결할 예정입니다.
    • 만약 해당 문제를 해결하신 분이 계시다면 도움 주시면 감사하겠습니다
  • SignUp의 VC는 두개의 뷰와 두개의 viewModel을 갖고 있습니다.
    • 해당 구조는 PhoneVerify의 재사용성을 높이기 위해 고안한 구조이며, 해당 구조에 대한 내용은 계정 찾기, 소셜계정 재연동뷰를 끝낸 후 PR에 정리해서 작성했습니다.

📸 스크린샷

기능 스크린샷
회원 인증
소셜 계정 연동

로그인 -> 회원가입뷰 transition 단계에서 레이아웃이 깨지는 현상

why!

📮 관련 이슈

@meltsplit meltsplit requested a review from L-j-h-c January 14, 2025 15:09
Copy link

height bot commented Jan 14, 2025

Link Height tasks by mentioning a task ID in the pull request title or commit messages, or description and comments with the keyword link (e.g. "Link T-123").

💡Tip: You can also use "Close T-X" to automatically close a task when the pull request is merged.

@meltsplit meltsplit self-assigned this Jan 14, 2025
@meltsplit meltsplit requested review from yungu0010, dlwogus0128 and seungchan2 and removed request for L-j-h-c January 14, 2025 15:09
Copy link
Contributor Author

@meltsplit meltsplit left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pr point

Comment on lines +12 to +19
@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
extension Publishers {

public struct WithLatestFrom<Upstream, Other> : Publisher where Upstream : Publisher, Other: Publisher, Upstream.Failure == Other.Failure {

public typealias Output = Other.Output
public typealias Failure = Upstream.Failure

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저는 RxSwift의 WithLatestFrom 연산자를 좋아하는데, Combine에 없어서 <Combine: withLatestFrom> 을 참고하여 연산자를 만들었습니다.

기능

  • 특정 이벤트가 왔을 때 해당 element을 다른 스트림의 최신값으로 변경해주는 기능입니다.
  • 만약 다른 스트림에 값이 없다면 값을 방출하지 않습니다.

Comment on lines +13 to +28
public struct PhoneVerifyPolicy {
public let phoneNumberCount: Int
private let _timeLimit: Duration
public var timeLimit: Int { Int(_timeLimit.components.seconds) }

public init(phoneNumberCount: Int, timeLimit: Duration) {
self.phoneNumberCount = phoneNumberCount
self._timeLimit = timeLimit
}
}

extension PhoneVerifyPolicy {
static let `default` = Self(phoneNumberCount: 11, timeLimit: .seconds(180))
static let stub = Self(phoneNumberCount: 11, timeLimit: .seconds(10))
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

기획서에 명시된 휴대폰번호 최대 길이 및 시간 제한에 관한 값은 Domain의 책임으로 결정했습니다. �해당 로직은 런타임에 변경될 일이 없기에 static 하게 선언 후 viewModel에서 해당 값을 사용하는 방식입니다.

Copy link
Contributor Author

@meltsplit meltsplit Jan 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_timeLimit은 객체 내부에서만 사용됨을 의미하며, Int가 아닌 Duration타입으로 구현한 이유는 Swift에서 권장하는 시간 단위를 사용하고 싶었기 때문이에용
단, 뷰모델에서 사용 편의성을 위해 외부로 노출하는 프로퍼티는 int 타입으로 하기위해 연산프로퍼티를 사용했습니다

Comment on lines 29 to 34
public protocol PhoneVerifyUseCase {
var policy: PhoneVerifyPolicy { get }

var sideEffect: PassthroughSubject<PhoneVerifyError, Never> { get }

func send(_ model: PhoneSendModel) -> AnyPublisher<Void, Never>
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

useCase는 앞서 말씀드린 policy를 프로퍼티로 갖습니다

Comment on lines +37 to +44

public func makeSignUp() -> SignUpPresentable {
let useCase = StubPhoneVerifyUseCase() // TODO
let vm = SignUpViewModel(useCase: useCase)
let subVM = PhoneVerifyViewModel(useCase: useCase)
let vc = SignUpVC(viewModel: vm, phoneVerifyViewModel: subVM)
return (vc, vm)
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

추후 변동 예정

Comment on lines 16 to 23
public var viewModelInput: PhoneVerifyViewModel.Input {
return .init(
sendButtonTapped: sendButton.publisher(for: .touchUpInside).mapVoid().asDriver(),
doneButtonTapped: doneButton.publisher(for: .touchUpInside).mapVoid().asDriver(),
phoneTextFieldText: phoneTextField.publisher(for: .editingChanged).map { $0.text ?? "" }.asDriver(),
codeTextFieldText: codeTextField.publisher(for: .editingChanged).map { $0.text ?? "" }.asDriver()
)
}
Copy link
Contributor Author

@meltsplit meltsplit Jan 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

번호인증뷰는 회원가입, 계정 찾기, 소셜계정 재설정에 사용됩니다.
따라서 PhoneVerifyView의 재사용성을 높이고자 UIView로 따로 뺐습니다.

이때 SignUpVC는 PhoneVerifyView와 SignUpViewModel의 값을 바인딩해야 할 것 입니다.
VC가 View 값을 바인딩 하기 위해 아래 두 방법을 생각해봤는데요

  • View의 프로퍼티의 접근제어자를 public으로 바꾸고 VC가 View 프로퍼티 의존
  • view에서 Input을 미리 프로퍼티로 정의하여 VC가 해당 프로퍼티만 의존.

1안의 단점은 객체지향의 접근제어자 깨지는 것이고, 2안의 단점은 View가 ViewModel을 알게되는 점인 것 같아요.
저는 개인적으로 MVVM 패턴에선 View와 VC의 역할 차이를 크게 두지 않는 편이라 2안을 택했습니다.

Comment on lines +81 to +86
input.sendButtonTapped
.handleEvents(receiveOutput: { _ in
output.isSent.send(true)
output.failDescription.send(nil) }
)
.withLatestFrom(input.phoneTextFieldText)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

withLatestFrom 사용 예시

Comment on lines +120 to +132
input.doneButtonTapped
.withLatestFrom(output.timerIsRunning)
.filter { $0 }
.withLatestFrom(Publishers.Zip(input.phoneTextFieldText, input.codeTextFieldText))
.map { PhoneVerifyModel(name: nil, phone: $0, code: $1, type: .register)}
.flatMap(useCase.verify)
.withUnretained(self)
.sink { owner, _ in
owner.timerCancellable = nil
output.verifySuccess.send(())
}
.store(in: cancelBag)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

withLatestFrom을 여러 스트림에 대하여 할 때는 Zip 연산자를 활용했습니다.

Comment on lines +21 to +30
public class SignUpVC: UIViewController, SignUpViewControllable {

//MARK: - Properties

private let phoneVerifyView = PhoneVerifyView()
private let oAuthView = SignUpOAuthView()

private let viewModel: SignUpViewModel
private let phoneVerifyViewModel: PhoneVerifyViewModel

Copy link
Contributor Author

@meltsplit meltsplit Jan 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1: 2: 2 구조입니다 !!!

스크린샷 2025-01-15 오전 12 24 58

Comment on lines +62 to +87
private let firstCircle = UILabel().then {
$0.text = "1"
$0.font = DSKitFontFamily.Suit.bold.font(size: 12)
$0.backgroundColor = DSKitAsset.Colors.blue400.color
$0.layer.cornerRadius = 11
$0.layer.masksToBounds = true
$0.textAlignment = .center
}

private let checkImageView = UIImageView().then {
$0.image = DSKitAsset.Assets.check.image.withAlignmentRectInsets(.init(top: -4, left: -4, bottom: -4, right: -4))
$0.contentMode = .scaleAspectFit
$0.backgroundColor = DSKitAsset.Colors.blue400.color
$0.layer.cornerRadius = 11
$0.layer.masksToBounds = true
$0.isHidden = true
}

private let secondCircle = UILabel().then {
$0.text = "2"
$0.font = DSKitFontFamily.Suit.bold.font(size: 12)
$0.backgroundColor = DSKitAsset.Colors.black40.color
$0.layer.cornerRadius = 11
$0.layer.masksToBounds = true
$0.textAlignment = .center
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

최근엔 재사용성을 위해 만든 뷰 역시 레거시로 남을 수 있다는 생각이 들어,
그냥 직관적으로 구현했습니다 ㅋ!

Comment on lines 24 to 32
struct Input {
let verifySuccess: Driver<Void>
var oAuth: OAuth

struct OAuth {
let googleLoginTapped: Driver<Void>
let appleLoginTapped: Driver<Void>
}
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Input 구조입니다.
스크린샷 2025-01-15 오전 12 33 15

@meltsplit meltsplit changed the title Feat/#467 signup UI [Feat] #467 - 회원가입 UI 구현 Jan 14, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant