diff --git a/KkuMulKum.xcodeproj/project.pbxproj b/KkuMulKum.xcodeproj/project.pbxproj index f9d0d405..886f114b 100644 --- a/KkuMulKum.xcodeproj/project.pbxproj +++ b/KkuMulKum.xcodeproj/project.pbxproj @@ -76,7 +76,7 @@ DD3072242C3C0EB200416D9F /* MyPromiseReadyInfoRequestModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD3072232C3C0EB200416D9F /* MyPromiseReadyInfoRequestModel.swift */; }; DD3072262C3C0F0B00416D9F /* PromiseLateInfoResponseModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD3072252C3C0F0B00416D9F /* PromiseLateInfoResponseModel.swift */; }; DD3072282C3C104D00416D9F /* ArrivalCompletionResponseModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD3072272C3C104D00416D9F /* ArrivalCompletionResponseModel.swift */; }; - DD4393762C412F4500EC1799 /* EnterInviteCodeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD43936D2C412F4500EC1799 /* EnterInviteCodeView.swift */; }; + DD4393762C412F4500EC1799 /* InviteCodeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD43936D2C412F4500EC1799 /* InviteCodeView.swift */; }; DD4393772C412F4500EC1799 /* CreateGroupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD43936E2C412F4500EC1799 /* CreateGroupView.swift */; }; DD4393782C412F4500EC1799 /* CheckInviteCodeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD43936F2C412F4500EC1799 /* CheckInviteCodeView.swift */; }; DD4393792C412F4500EC1799 /* JoinButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD4393702C412F4500EC1799 /* JoinButtonView.swift */; }; @@ -84,6 +84,7 @@ DD43937B2C412F4500EC1799 /* CreateGroupViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD4393732C412F4500EC1799 /* CreateGroupViewController.swift */; }; DD43937C2C412F4500EC1799 /* EnterInviteCodeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD4393742C412F4500EC1799 /* EnterInviteCodeViewController.swift */; }; DD43937D2C412F4500EC1799 /* CheckInviteCodeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD4393752C412F4500EC1799 /* CheckInviteCodeViewController.swift */; }; + DD43937F2C41357800EC1799 /* InviteCodeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD43937E2C41357800EC1799 /* InviteCodeViewModel.swift */; }; DD931B6B2C3D9EBB00526452 /* ReadyStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD931B6A2C3D9EBB00526452 /* ReadyStatusView.swift */; }; DD931B6E2C3DA27F00526452 /* ParticipantCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD931B6D2C3DA27F00526452 /* ParticipantCollectionViewCell.swift */; }; DD931B722C3DA92700526452 /* EnterReadyInfoButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD931B712C3DA92700526452 /* EnterReadyInfoButtonView.swift */; }; @@ -194,7 +195,7 @@ DD3072232C3C0EB200416D9F /* MyPromiseReadyInfoRequestModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyPromiseReadyInfoRequestModel.swift; sourceTree = ""; }; DD3072252C3C0F0B00416D9F /* PromiseLateInfoResponseModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PromiseLateInfoResponseModel.swift; sourceTree = ""; }; DD3072272C3C104D00416D9F /* ArrivalCompletionResponseModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArrivalCompletionResponseModel.swift; sourceTree = ""; }; - DD43936D2C412F4500EC1799 /* EnterInviteCodeView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EnterInviteCodeView.swift; sourceTree = ""; }; + DD43936D2C412F4500EC1799 /* InviteCodeView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InviteCodeView.swift; sourceTree = ""; }; DD43936E2C412F4500EC1799 /* CreateGroupView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CreateGroupView.swift; sourceTree = ""; }; DD43936F2C412F4500EC1799 /* CheckInviteCodeView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CheckInviteCodeView.swift; sourceTree = ""; }; DD4393702C412F4500EC1799 /* JoinButtonView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JoinButtonView.swift; sourceTree = ""; }; @@ -202,6 +203,7 @@ DD4393732C412F4500EC1799 /* CreateGroupViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CreateGroupViewController.swift; sourceTree = ""; }; DD4393742C412F4500EC1799 /* EnterInviteCodeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EnterInviteCodeViewController.swift; sourceTree = ""; }; DD4393752C412F4500EC1799 /* CheckInviteCodeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CheckInviteCodeViewController.swift; sourceTree = ""; }; + DD43937E2C41357800EC1799 /* InviteCodeViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InviteCodeViewModel.swift; sourceTree = ""; }; DD931B6A2C3D9EBB00526452 /* ReadyStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReadyStatusView.swift; sourceTree = ""; }; DD931B6D2C3DA27F00526452 /* ParticipantCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParticipantCollectionViewCell.swift; sourceTree = ""; }; DD931B712C3DA92700526452 /* EnterReadyInfoButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnterReadyInfoButtonView.swift; sourceTree = ""; }; @@ -479,6 +481,7 @@ children = ( DD43936C2C412F4500EC1799 /* View */, DD4393712C412F4500EC1799 /* ViewController */, + DD4393802C41357D00EC1799 /* ViewModel */, ); path = GroupCreate; sourceTree = ""; @@ -486,7 +489,7 @@ DD43936C2C412F4500EC1799 /* View */ = { isa = PBXGroup; children = ( - DD43936D2C412F4500EC1799 /* EnterInviteCodeView.swift */, + DD43936D2C412F4500EC1799 /* InviteCodeView.swift */, DD43936E2C412F4500EC1799 /* CreateGroupView.swift */, DD43936F2C412F4500EC1799 /* CheckInviteCodeView.swift */, DD4393702C412F4500EC1799 /* JoinButtonView.swift */, @@ -505,6 +508,14 @@ path = ViewController; sourceTree = ""; }; + DD4393802C41357D00EC1799 /* ViewModel */ = { + isa = PBXGroup; + children = ( + DD43937E2C41357800EC1799 /* InviteCodeViewModel.swift */, + ); + path = ViewModel; + sourceTree = ""; + }; DD865B652C3920F600C351A2 /* Onboarding */ = { isa = PBXGroup; children = ( @@ -1057,6 +1068,7 @@ DD931B722C3DA92700526452 /* EnterReadyInfoButtonView.swift in Sources */, A3FB18512C3BF531001483E5 /* RegisterMeetingsResponseModel.swift in Sources */, 789AD4B32C3C0093002E2688 /* SocialLoginResponseModel.swift in Sources */, + DD43937F2C41357800EC1799 /* InviteCodeViewModel.swift in Sources */, DE159D352C406E1600425101 /* MyPageNavigationView.swift in Sources */, DE9E188B2C3BC92500DB76B4 /* EmptyModel.swift in Sources */, DDA2EE732C385EB9007C6059 /* MainTabBarController.swift in Sources */, @@ -1087,7 +1099,7 @@ DDAF1C902C3D6E3D008A37D3 /* PromiseViewModel.swift in Sources */, DE254AAA2C31190E00A4015E /* UIStackView+.swift in Sources */, DE159D362C406E1600425101 /* MyPageViewController.swift in Sources */, - DD4393762C412F4500EC1799 /* EnterInviteCodeView.swift in Sources */, + DD4393762C412F4500EC1799 /* InviteCodeView.swift in Sources */, DED5DBF02C345317006ECE7E /* BaseCollectionViewCell.swift in Sources */, DE32D1D42C3BFB56006848DF /* UpdateProfileNameModel.swift in Sources */, DE6D4D132C3F14D80005584B /* MeetingMemberCell.swift in Sources */, diff --git a/KkuMulKum.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/KkuMulKum.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index e1e5105d..45b79d88 100644 --- a/KkuMulKum.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/KkuMulKum.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,4 @@ { - "originHash" : "9a47217a5c175e3de895c155c13de64f1d563d13e297ec224a467854897a75be", "pins" : [ { "identity" : "abseil-cpp-binary", @@ -209,5 +208,5 @@ } } ], - "version" : 3 + "version" : 2 } diff --git a/KkuMulKum/Application/SceneDelegate.swift b/KkuMulKum/Application/SceneDelegate.swift index e75269f7..11d514e9 100644 --- a/KkuMulKum/Application/SceneDelegate.swift +++ b/KkuMulKum/Application/SceneDelegate.swift @@ -18,7 +18,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { ) { guard let windowScene = (scene as? UIWindowScene) else { return } self.window = UIWindow(windowScene: windowScene) - self.window?.rootViewController = NicknameViewController() + self.window?.rootViewController = MainTabBarController() self.window?.makeKeyAndVisible() } diff --git a/KkuMulKum/Source/GroupCreate/View/EnterInviteCodeView.swift b/KkuMulKum/Source/GroupCreate/View/InviteCodeView.swift similarity index 52% rename from KkuMulKum/Source/GroupCreate/View/EnterInviteCodeView.swift rename to KkuMulKum/Source/GroupCreate/View/InviteCodeView.swift index 511b33e2..592fcc6a 100644 --- a/KkuMulKum/Source/GroupCreate/View/EnterInviteCodeView.swift +++ b/KkuMulKum/Source/GroupCreate/View/InviteCodeView.swift @@ -1,5 +1,5 @@ // -// EnterInviteCodeView.swift +// InviteCodeView.swift // KkuMulKum // // Created by YOUJIM on 7/12/24. @@ -7,19 +7,32 @@ import UIKit -class EnterInviteCodeView: BaseView { +class InviteCodeView: BaseView { private let mainTitleLabel: UILabel = UILabel().then { $0.setText("모임 초대 코드를\n입력해 주세요", style: .head01) } - private let inviteCodeTextField: CustomTextField = CustomTextField( + let inviteCodeTextField: CustomTextField = CustomTextField( placeHolder: "모임 초대 코드를 입력해 주세요" ) + let checkImageView: UIImageView = UIImageView().then { + $0.image = .iconCheck.withTintColor(.maincolor) + $0.contentMode = .scaleAspectFit + $0.isHidden = true + } + + let errorLabel: UILabel = UILabel().then { + $0.setText("모임 초대 코드를 다시 확인해주세요.", style: .caption02, color: .mainred) + $0.isHidden = true + } + let presentButton: CustomButton = CustomButton(title: "모임 가입하기", isEnabled: false) override func setupView() { - addSubviews(mainTitleLabel, inviteCodeTextField, presentButton) + backgroundColor = .white + + addSubviews(mainTitleLabel, inviteCodeTextField, checkImageView, errorLabel, presentButton) } override func setupAutoLayout() { @@ -37,6 +50,18 @@ class EnterInviteCodeView: BaseView { $0.leading.equalTo(mainTitleLabel) } + checkImageView.snp.makeConstraints { + $0.centerY.equalTo(inviteCodeTextField) + $0.trailing.equalTo(inviteCodeTextField.snp.trailing).inset(12) + $0.height.equalTo(Screen.height(24)) + $0.width.equalTo(checkImageView.snp.height) + } + + errorLabel.snp.makeConstraints { + $0.top.equalTo(inviteCodeTextField.snp.bottom).offset(8) + $0.leading.equalTo(inviteCodeTextField) + } + presentButton.snp.makeConstraints { $0.bottom.equalToSuperview().inset(50) $0.horizontalEdges.equalToSuperview().inset(14) diff --git a/KkuMulKum/Source/GroupCreate/ViewController/EnterInviteCodeViewController.swift b/KkuMulKum/Source/GroupCreate/ViewController/EnterInviteCodeViewController.swift index 15799193..2a7ff098 100644 --- a/KkuMulKum/Source/GroupCreate/ViewController/EnterInviteCodeViewController.swift +++ b/KkuMulKum/Source/GroupCreate/ViewController/EnterInviteCodeViewController.swift @@ -8,14 +8,88 @@ import UIKit class EnterInviteCodeViewController: BaseViewController { - private let enterInviteCodeView: EnterInviteCodeView = EnterInviteCodeView() + private let inviteCodeViewModel: InviteCodeViewModel = InviteCodeViewModel() + + private let inviteCodeView: InviteCodeView = InviteCodeView() override func loadView() { - view = enterInviteCodeView + view = inviteCodeView + } + + override func viewDidLoad() { + setupBinding() + setupTapGesture() } override func setupView() { setupNavigationBarTitle(with: "내 모임 추가하기") setupNavigationBarBackButton() } + + private func setupBinding() { + inviteCodeViewModel.inviteCodeState.bind(with: self) { owner, state in + switch state { + case .empty: + self.inviteCodeView.inviteCodeTextField.layer.borderColor = UIColor.gray3.cgColor + self.inviteCodeView.errorLabel.isHidden = true + self.inviteCodeView.checkImageView.isHidden = true + case.selected: + self.inviteCodeView.inviteCodeTextField.layer.borderColor = UIColor.maincolor.cgColor + self.inviteCodeView.errorLabel.isHidden = true + self.inviteCodeView.checkImageView.isHidden = true + case .invalid: + self.inviteCodeView.inviteCodeTextField.layer.borderColor = UIColor.mainred.cgColor + self.inviteCodeView.errorLabel.isHidden = false + self.inviteCodeView.checkImageView.isHidden = true + case .valid: + self.inviteCodeView.inviteCodeTextField.layer.borderColor = UIColor.maincolor.cgColor + self.inviteCodeView.errorLabel.isHidden = true + self.inviteCodeView.checkImageView.isHidden = false + } + } + } + + override func setupAction() { + inviteCodeView.inviteCodeTextField.addTarget(self, action: #selector(textFieldDidChange(_:)), for: .editingChanged) + inviteCodeView.presentButton.addTarget(self, action: #selector(nextButtonTapped), for: .touchUpInside) + } + + override func setupDelegate() { + inviteCodeView.inviteCodeTextField.delegate = self + inviteCodeView.inviteCodeTextField.returnKeyType = .done + } + + private func setupTapGesture() { + let tapGesture = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard)) + + view.addGestureRecognizer(tapGesture) + } + + @objc private func nextButtonTapped() { + // TODO: 서버 연결할 때 데이터 바인딩해서 화면 전환 시키기 + let promiseViewController = PromiseViewController() + + promiseViewController.modalPresentationStyle = .fullScreen + + navigationController?.pushViewController(promiseViewController, animated: true) + } + + @objc private func textFieldDidChange(_ textField: UITextField) { + inviteCodeViewModel.validateCode(textField.text ?? "") + } + + @objc private func dismissKeyboard() { + view.endEditing(true) + inviteCodeView.inviteCodeTextField.layer.borderColor = UIColor.gray3.cgColor + } } + +extension EnterInviteCodeViewController: UITextFieldDelegate { + func textFieldShouldReturn(_ textField: UITextField) -> Bool { + textField.resignFirstResponder() + inviteCodeView.inviteCodeTextField.layer.borderColor = UIColor.gray3.cgColor + + return true + } +} + diff --git a/KkuMulKum/Source/GroupCreate/ViewModel/InviteCodeViewModel.swift b/KkuMulKum/Source/GroupCreate/ViewModel/InviteCodeViewModel.swift new file mode 100644 index 00000000..366d578a --- /dev/null +++ b/KkuMulKum/Source/GroupCreate/ViewModel/InviteCodeViewModel.swift @@ -0,0 +1,42 @@ +// +// InviteCodeViewModel.swift +// KkuMulKum +// +// Created by YOUJIM on 7/12/24. +// + +import Foundation + +enum InviteCodeState { + case empty + case selected + case valid + case invalid +} + +class InviteCodeViewModel { + let inviteCode = ObservablePattern("") + let inviteCodeState = ObservablePattern(.empty) + let codeErrorMessage = ObservablePattern(nil) + let isNextButtonEnabled = ObservablePattern(false) + + private let codeRegex = "{1,5}$" + + func validateCode(_ code: String) { + inviteCode.value = code + + if code.isEmpty { + inviteCodeState.value = .empty + codeErrorMessage.value = nil + isNextButtonEnabled.value = false + } else if code.range(of: codeRegex, options: .regularExpression) != nil { + inviteCodeState.value = .valid + codeErrorMessage.value = nil + isNextButtonEnabled.value = true + } else { + inviteCodeState.value = .invalid + codeErrorMessage.value = "한글, 영문, 숫자만을 사용해 총 5자 이내로 입력해주세요." + isNextButtonEnabled.value = false + } + } +} diff --git a/KkuMulKum/Source/MeetingInfo/ViewController/MeetingInfoViewController.swift b/KkuMulKum/Source/MeetingInfo/ViewController/MeetingInfoViewController.swift index 7e5486db..7d2ac216 100644 --- a/KkuMulKum/Source/MeetingInfo/ViewController/MeetingInfoViewController.swift +++ b/KkuMulKum/Source/MeetingInfo/ViewController/MeetingInfoViewController.swift @@ -55,7 +55,7 @@ final class MeetingInfoViewController: BaseViewController { } override func setupView() { - setupNavigationBar() + setupNavigationBarBackButton() } } @@ -150,30 +150,3 @@ extension MeetingInfoViewController: MeetingMemberCellDelegate { present(viewController, animated: true) } } - -private extension MeetingInfoViewController { - func setupNavigationBar() { - title = "" - - navigationController?.navigationBar.titleTextAttributes = [ - .foregroundColor: UIColor.gray8, - .font: UIFont.pretendard(.body03) - ] - - let backButton = UIBarButtonItem( - image: .iconBack, - style: .plain, - target: self, - action: #selector(backButtonDidTap) - ).then { - $0.tintColor = .black - } - - navigationItem.leftBarButtonItem = backButton - } - - @objc - func backButtonDidTap() { - navigationController?.popViewController(animated: true) - } -}