diff --git a/DontBe-iOS/DontBe-iOS.xcodeproj/project.pbxproj b/DontBe-iOS/DontBe-iOS.xcodeproj/project.pbxproj index fe96fa7a..c7150b00 100644 --- a/DontBe-iOS/DontBe-iOS.xcodeproj/project.pbxproj +++ b/DontBe-iOS/DontBe-iOS.xcodeproj/project.pbxproj @@ -13,12 +13,16 @@ 2A2671FF2B4C3AF0009D214F /* Publisher+UIControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A2671FE2B4C3AF0009D214F /* Publisher+UIControl.swift */; }; 2A2672022B4C3B44009D214F /* ViewModelType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A2672012B4C3B44009D214F /* ViewModelType.swift */; }; 2A2672052B4C3C00009D214F /* CancelBag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A2672042B4C3C00009D214F /* CancelBag.swift */; }; + 2A31FF572B4F1E0400FEEED9 /* String+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A31FF562B4F1E0400FEEED9 /* String+.swift */; }; + 2A31FF592B4F3A8B00FEEED9 /* UserInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A31FF582B4F3A8B00FEEED9 /* UserInfo.swift */; }; 2A51AE852B4B05AA00FF770A /* SplashViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A51AE842B4B05AA00FF770A /* SplashViewController.swift */; }; 2A6D54C12B479B4300F9891E /* adjusted+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A6D54C02B479B4300F9891E /* adjusted+.swift */; }; 2A6D54C32B493A8400F9891E /* UIFont+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A6D54C22B493A8400F9891E /* UIFont+.swift */; }; 2A6D54C62B493E3F00F9891E /* Pretendard-SemiBold.otf in Resources */ = {isa = PBXBuildFile; fileRef = 2A6D54C42B493E3F00F9891E /* Pretendard-SemiBold.otf */; }; 2A6D54C72B493E3F00F9891E /* Pretendard-Regular.otf in Resources */ = {isa = PBXBuildFile; fileRef = 2A6D54C52B493E3F00F9891E /* Pretendard-Regular.otf */; }; 2A84465A2B4EE8B400F98F2A /* JoinProfileViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A8446592B4EE8B400F98F2A /* JoinProfileViewController.swift */; }; + 2A84465C2B4EEC6C00F98F2A /* JoinProfileViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A84465B2B4EEC6C00F98F2A /* JoinProfileViewModel.swift */; }; + 2A84465E2B4EF41900F98F2A /* JoinProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A84465D2B4EF41900F98F2A /* JoinProfileView.swift */; }; 2A8D70B12B4C4D8F009F4C6C /* KakaoSDK in Frameworks */ = {isa = PBXBuildFile; productRef = 2A8D70B02B4C4D8F009F4C6C /* KakaoSDK */; }; 2A8D70B42B4C999F009F4C6C /* CustomButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A8D70B32B4C999F009F4C6C /* CustomButton.swift */; }; 2A8D70B82B4C9A59009F4C6C /* OnboardingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A8D70B72B4C9A59009F4C6C /* OnboardingViewController.swift */; }; @@ -74,12 +78,16 @@ 2A2672012B4C3B44009D214F /* ViewModelType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewModelType.swift; sourceTree = ""; }; 2A2672042B4C3C00009D214F /* CancelBag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CancelBag.swift; sourceTree = ""; }; 2A26720D2B4C40CE009D214F /* Config.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Config.xcconfig; sourceTree = ""; }; + 2A31FF562B4F1E0400FEEED9 /* String+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+.swift"; sourceTree = ""; }; + 2A31FF582B4F3A8B00FEEED9 /* UserInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserInfo.swift; sourceTree = ""; }; 2A51AE842B4B05AA00FF770A /* SplashViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SplashViewController.swift; sourceTree = ""; }; 2A6D54C02B479B4300F9891E /* adjusted+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "adjusted+.swift"; sourceTree = ""; }; 2A6D54C22B493A8400F9891E /* UIFont+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIFont+.swift"; sourceTree = ""; }; 2A6D54C42B493E3F00F9891E /* Pretendard-SemiBold.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Pretendard-SemiBold.otf"; sourceTree = ""; }; 2A6D54C52B493E3F00F9891E /* Pretendard-Regular.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Pretendard-Regular.otf"; sourceTree = ""; }; 2A8446592B4EE8B400F98F2A /* JoinProfileViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JoinProfileViewController.swift; sourceTree = ""; }; + 2A84465B2B4EEC6C00F98F2A /* JoinProfileViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JoinProfileViewModel.swift; sourceTree = ""; }; + 2A84465D2B4EF41900F98F2A /* JoinProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JoinProfileView.swift; sourceTree = ""; }; 2A8D70B32B4C999F009F4C6C /* CustomButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomButton.swift; sourceTree = ""; }; 2A8D70B72B4C9A59009F4C6C /* OnboardingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingViewController.swift; sourceTree = ""; }; 2A8D70BC2B4D61A1009F4C6C /* OnboardingDummy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingDummy.swift; sourceTree = ""; }; @@ -248,6 +256,7 @@ isa = PBXGroup; children = ( 2A8D70D22B4DD360009F4C6C /* JoinAgreeViewModel.swift */, + 2A84465B2B4EEC6C00F98F2A /* JoinProfileViewModel.swift */, ); path = ViewModels; sourceTree = ""; @@ -266,6 +275,7 @@ children = ( 2AC9FB1A2B4DE77400D31071 /* AgreementListCustomView.swift */, 2AC9FB1E2B4E634A00D31071 /* JoinAgreeView.swift */, + 2A84465D2B4EF41900F98F2A /* JoinProfileView.swift */, ); path = Views; sourceTree = ""; @@ -366,6 +376,7 @@ 2A2671FE2B4C3AF0009D214F /* Publisher+UIControl.swift */, 2A8D70C42B4D8079009F4C6C /* UIViewController+.swift */, 2A8D70CB2B4DA7BD009F4C6C /* UITextField+.swift */, + 2A31FF562B4F1E0400FEEED9 /* String+.swift */, ); path = Extension; sourceTree = ""; @@ -488,6 +499,7 @@ 2A2672042B4C3C00009D214F /* CancelBag.swift */, 2A8D70C22B4D7FF5009F4C6C /* BackButton.swift */, 2A8D70B32B4C999F009F4C6C /* CustomButton.swift */, + 2A31FF582B4F3A8B00FEEED9 /* UserInfo.swift */, ); path = Helpers; sourceTree = ""; @@ -573,7 +585,7 @@ files = ( 2A8D70CA2B4D9787009F4C6C /* IntroductionView.swift in Sources */, 2F87354A2B4C427000E55552 /* UICollectionViewRegisterable.swift in Sources */, - 2A8D70CA2B4D9787009F4C6C /* OnboardingEndingView.swift in Sources */, + 2A8D70CA2B4D9787009F4C6C /* IntroductionView.swift in Sources */, 3C6193172B3A7A7B00220CEB /* UIStackView+.swift in Sources */, 3CE9C12E2B4C08AE0086E4A3 /* WriteTextView.swift in Sources */, 2A8D70BD2B4D61A1009F4C6C /* OnboardingDummy.swift in Sources */, @@ -604,14 +616,18 @@ 2F87354C2B4D28D700E55552 /* HomeCollectionFooterView.swift in Sources */, 2F8735482B4C355100E55552 /* HomeCollectionViewCell.swift in Sources */, 3CE9C12A2B4BE6780086E4A3 /* WriteViewController.swift in Sources */, + 2A84465C2B4EEC6C00F98F2A /* JoinProfileViewModel.swift in Sources */, 3C2854FD2B3A9FD800369C99 /* ExampleViewController.swift in Sources */, 2AAEFC992B4A9E3B00C2D323 /* DontBeTabBarController.swift in Sources */, + 2A31FF592B4F3A8B00FEEED9 /* UserInfo.swift in Sources */, 2AAEFC972B4A9C3700C2D323 /* DontBeTabBarItem.swift in Sources */, 3C0920DE2B4D98CD003BD080 /* DontBeToastView.swift in Sources */, 3CE9C1302B4C2F740086E4A3 /* UIImageView+.swift in Sources */, 2A51AE852B4B05AA00FF770A /* SplashViewController.swift in Sources */, + 2A84465E2B4EF41900F98F2A /* JoinProfileView.swift in Sources */, 3CE9C1322B4C3C2D0086E4A3 /* UITextView+.swift in Sources */, 2A8D70BF2B4D68E3009F4C6C /* OnboardingEndingViewController.swift in Sources */, + 2A31FF572B4F1E0400FEEED9 /* String+.swift in Sources */, 2A51AE852B4B05AA00FF770A /* SplashViewController.swift in Sources */, 2A8D70C32B4D7FF5009F4C6C /* BackButton.swift in Sources */, 2A8D70D32B4DD360009F4C6C /* JoinAgreeViewModel.swift in Sources */, diff --git a/DontBe-iOS/DontBe-iOS/Global/Extension/String+.swift b/DontBe-iOS/DontBe-iOS/Global/Extension/String+.swift new file mode 100644 index 00000000..3b6483d4 --- /dev/null +++ b/DontBe-iOS/DontBe-iOS/Global/Extension/String+.swift @@ -0,0 +1,17 @@ +// +// String+.swift +// DontBe-iOS +// +// Created by 변희주 on 1/11/24. +// + +extension String { + // 글자가 자음인지 체크 + var isConsonant: Bool { + guard let scalar = UnicodeScalar(self)?.value else { + return false + } + let consonantScalarRange: ClosedRange = 12593...12622 + return consonantScalarRange ~= scalar + } +} diff --git a/DontBe-iOS/DontBe-iOS/Global/Literals/ImageLiterals.swift b/DontBe-iOS/DontBe-iOS/Global/Literals/ImageLiterals.swift index bfbb690e..15ae5be1 100644 --- a/DontBe-iOS/DontBe-iOS/Global/Literals/ImageLiterals.swift +++ b/DontBe-iOS/DontBe-iOS/Global/Literals/ImageLiterals.swift @@ -11,6 +11,7 @@ enum ImageLiterals { enum Common { static var btnBack: UIImage { .load(name: "btn_back") } static var logoSymbol: UIImage { .load(name: "logo_symbol") } + static var imgProfile: UIImage { .load(name: "img_profile") } } enum TabBar { @@ -36,7 +37,6 @@ enum ImageLiterals { static var imgTwoTitle: UIImage { .load(name: "title_second") } static var imgThirdTitle: UIImage { .load(name: "title_third") } static var imgFourthTitle: UIImage { .load(name: "title_fourth") } - static var imgProfile: UIImage { .load(name: "img_profile") } } enum Login { @@ -52,6 +52,7 @@ enum ImageLiterals { static var btnNecessary: UIImage { .load(name: "btn_necessary") } static var btnSelect: UIImage { .load(name: "btn_select") } static var btnView: UIImage { .load(name: "btn_view") } + static var btnPlus: UIImage { .load(name: "btn_plus") } } enum Home { diff --git a/DontBe-iOS/DontBe-iOS/Global/Resources/Assets.xcassets/button/btn_plus.imageset/Contents.json b/DontBe-iOS/DontBe-iOS/Global/Resources/Assets.xcassets/button/btn_plus.imageset/Contents.json new file mode 100644 index 00000000..a6d8687c --- /dev/null +++ b/DontBe-iOS/DontBe-iOS/Global/Resources/Assets.xcassets/button/btn_plus.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "btn_plus.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "btn_plus@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "btn_plus@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/DontBe-iOS/DontBe-iOS/Global/Resources/Assets.xcassets/button/btn_plus.imageset/btn_plus.png b/DontBe-iOS/DontBe-iOS/Global/Resources/Assets.xcassets/button/btn_plus.imageset/btn_plus.png new file mode 100644 index 00000000..006619fd Binary files /dev/null and b/DontBe-iOS/DontBe-iOS/Global/Resources/Assets.xcassets/button/btn_plus.imageset/btn_plus.png differ diff --git a/DontBe-iOS/DontBe-iOS/Global/Resources/Assets.xcassets/button/btn_plus.imageset/btn_plus@2x.png b/DontBe-iOS/DontBe-iOS/Global/Resources/Assets.xcassets/button/btn_plus.imageset/btn_plus@2x.png new file mode 100644 index 00000000..56da7202 Binary files /dev/null and b/DontBe-iOS/DontBe-iOS/Global/Resources/Assets.xcassets/button/btn_plus.imageset/btn_plus@2x.png differ diff --git a/DontBe-iOS/DontBe-iOS/Global/Resources/Assets.xcassets/button/btn_plus.imageset/btn_plus@3x.png b/DontBe-iOS/DontBe-iOS/Global/Resources/Assets.xcassets/button/btn_plus.imageset/btn_plus@3x.png new file mode 100644 index 00000000..4a4f9317 Binary files /dev/null and b/DontBe-iOS/DontBe-iOS/Global/Resources/Assets.xcassets/button/btn_plus.imageset/btn_plus@3x.png differ diff --git a/DontBe-iOS/DontBe-iOS/Presentation/Helpers/UserInfo.swift b/DontBe-iOS/DontBe-iOS/Presentation/Helpers/UserInfo.swift new file mode 100644 index 00000000..f0088b67 --- /dev/null +++ b/DontBe-iOS/DontBe-iOS/Presentation/Helpers/UserInfo.swift @@ -0,0 +1,12 @@ +// +// UserInfo.swift +// DontBe-iOS +// +// Created by 변희주 on 1/11/24. +// + +import Foundation + +struct UserInfo { + let userNickname: String +} diff --git a/DontBe-iOS/DontBe-iOS/Presentation/Join/ViewControllers/JoinAgreementViewController.swift b/DontBe-iOS/DontBe-iOS/Presentation/Join/ViewControllers/JoinAgreementViewController.swift index 3966ffc6..f42235e8 100644 --- a/DontBe-iOS/DontBe-iOS/Presentation/Join/ViewControllers/JoinAgreementViewController.swift +++ b/DontBe-iOS/DontBe-iOS/Presentation/Join/ViewControllers/JoinAgreementViewController.swift @@ -4,7 +4,7 @@ // // Created by 변희주 on 1/10/24. // - +import Combine import UIKit import SnapKit @@ -16,17 +16,18 @@ final class JoinAgreementViewController: UIViewController { private var cancelBag = CancelBag() private let viewModel: JoinAgreeViewModel - private lazy var backButtonTapped = navigationBackButton.publisher(for: .touchUpInside).map { _ in }.eraseToAnyPublisher() + private lazy var backButtonTapped = self.navigationBackButton.publisher(for: .touchUpInside).map { _ in }.eraseToAnyPublisher() private lazy var allCheckButtonTapped = self.originView.allCheck.checkButton.publisher(for: .touchUpInside).map { _ in }.eraseToAnyPublisher() private lazy var firstCheck = self.originView.firstCheckView.checkButton.publisher(for: .touchUpInside).map { _ in }.eraseToAnyPublisher() private lazy var secondCheck = self.originView.secondCheckView.checkButton.publisher(for: .touchUpInside).map { _ in }.eraseToAnyPublisher() private lazy var thirdCheck = self.originView.thirdCheckView.checkButton.publisher(for: .touchUpInside).map { _ in }.eraseToAnyPublisher() private lazy var fourtchCheck = self.originView.fourthCheckView.checkButton.publisher(for: .touchUpInside).map { _ in }.eraseToAnyPublisher() + private lazy var nextButtonTapped = self.originView.nextActiveButton.publisher(for: .touchUpInside).map { _ in }.eraseToAnyPublisher() // MARK: - UI Components - private let navigationBackButton = BackButton() + private var navigationBackButton = BackButton() private let originView = JoinAgreeView() // MARK: - Life Cycles @@ -51,16 +52,16 @@ final class JoinAgreementViewController: UIViewController { super.viewDidLoad() setUI() - setHierarchy() - setLayout() bindViewModel() } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) - + self.navigationController?.navigationBar.isHidden = false self.navigationItem.hidesBackButton = true + setHierarchy() + setLayout() } } @@ -74,7 +75,6 @@ extension JoinAgreementViewController { private func setHierarchy() { self.navigationController?.navigationBar.addSubviews(navigationBackButton) - } private func setLayout() { @@ -91,7 +91,8 @@ extension JoinAgreementViewController { firstCheckButtonTapped: firstCheck, secondCheckButtonTapped: secondCheck, thirdCheckButtonTapped: thirdCheck, - fourthCheckButtonTapped: fourtchCheck) + fourthCheckButtonTapped: fourtchCheck, + nextButtonTapped: nextButtonTapped) let output = self.viewModel.transform(from: input, cancelBag: self.cancelBag) @@ -158,5 +159,12 @@ extension JoinAgreementViewController { } } .store(in: self.cancelBag) + + output.pushViewController + .sink { _ in + let viewController = JoinProfileViewController(viewModel: JoinProfileViewModel()) + self.navigationController?.pushViewController(viewController, animated: true) + } + .store(in: self.cancelBag) } } diff --git a/DontBe-iOS/DontBe-iOS/Presentation/Join/ViewControllers/JoinProfileViewController.swift b/DontBe-iOS/DontBe-iOS/Presentation/Join/ViewControllers/JoinProfileViewController.swift index 72c33be3..ce6f067d 100644 --- a/DontBe-iOS/DontBe-iOS/Presentation/Join/ViewControllers/JoinProfileViewController.swift +++ b/DontBe-iOS/DontBe-iOS/Presentation/Join/ViewControllers/JoinProfileViewController.swift @@ -5,4 +5,166 @@ // Created by 변희주 on 1/11/24. // -import Foundation +import UIKit + +import SnapKit + +final class JoinProfileViewController: UIViewController { + + // MARK: - Properties + + private var cancelBag = CancelBag() + private let viewModel: JoinProfileViewModel + + private lazy var backButtonTapped = navigationBackButton.publisher(for: .touchUpInside).map { _ in }.eraseToAnyPublisher() + private lazy var duplicationCheckButtonTapped = self.originView.duplicationCheckButton.publisher(for: .touchUpInside).map { _ in + return self.originView.nickNameTextField.text ?? "" + }.eraseToAnyPublisher() + private lazy var finishButtonTapped = self.originView.finishActiveButton.publisher(for: .touchUpInside).map { _ in }.eraseToAnyPublisher() + + // MARK: - UI Components + + private let navigationBackButton = BackButton() + private let originView = JoinProfileView() + + override func touchesBegan(_ touches: Set, with event: UIEvent?) { + view.endEditing(true) + } + + // MARK: - Life Cycles + + init(viewModel: JoinProfileViewModel) { + self.viewModel = viewModel + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func loadView() { + super.loadView() + + view = originView + } + + override func viewDidLoad() { + super.viewDidLoad() + + setUI() + setHierarchy() + setLayout() + setDelegate() + bindViewModel() + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + self.navigationController?.navigationBar.isHidden = false + self.navigationItem.hidesBackButton = true + } +} + +// MARK: - Extensions + +extension JoinProfileViewController { + private func setUI() { + self.view.backgroundColor = .donWhite + self.navigationItem.title = StringLiterals.Join.joinNavigationTitle + } + + private func setHierarchy() { + self.navigationController?.navigationBar.addSubviews(navigationBackButton) + + } + + private func setLayout() { + navigationBackButton.snp.makeConstraints { + $0.centerY.equalToSuperview() + $0.leading.equalToSuperview().inset(23.adjusted) + } + } + + private func setDelegate() { + self.originView.nickNameTextField.delegate = self + } + + private func bindViewModel() { + let input = JoinProfileViewModel.Input(backButtonTapped: backButtonTapped, duplicationCheckButtonTapped: duplicationCheckButtonTapped, finishButtonTapped: finishButtonTapped) + let output = self.viewModel.transform(from: input, cancelBag: self.cancelBag) + + output.pushOrPopViewController + .sink { value in + if value == 0 { + self.navigationController?.popViewController(animated: true) + } else { + // UserInfo 인스턴스 생성 + let userNickname = UserInfo(userNickname: self.originView.nickNameTextField.text ?? "") + // Local DB에 저장 + UserDefaults.standard.set(userNickname.userNickname, forKey: "nickname") + + let viewContoller = OnboardingViewController() + self.navigationController?.pushViewController(viewContoller, animated: true) + } + } + .store(in: self.cancelBag) + + output.isEnable + .sink { isTrue in + self.originView.finishActiveButton.isHidden = !isTrue + if isTrue { + self.originView.duplicationCheckDescription.text = StringLiterals.Join.duplicationPass + self.originView.duplicationCheckDescription.textColor = .donSecondary + } else { + self.originView.duplicationCheckDescription.text = StringLiterals.Join.duplicationNotPass + self.originView.duplicationCheckDescription.textColor = .donError + } + } + .store(in: self.cancelBag) + } +} + +// MARK: - UITextFieldDelegate + +extension JoinProfileViewController: UITextFieldDelegate { + func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { + let maxLength = 12 // 글자수 제한 + let oldText = textField.text ?? "" // 입력하기 전 textField에 표시되어있던 text + let addedText = string // 입력한 text + let newText = oldText + addedText // 입력하기 전 text와 입력한 후 text를 합침 + let newTextLength = newText.count // 합쳐진 text의 길이 + + // 글자수 제한 + if newTextLength <= maxLength { + return true + } + + let lastWordOfOldText = String(oldText[oldText.index(before: oldText.endIndex)]) // 입력하기 전 text의 마지막 글자 + let separatedCharacters = lastWordOfOldText.decomposedStringWithCanonicalMapping.unicodeScalars.map{ String($0) } // 입력하기 전 text의 마지막 글자를 자음과 모음으로 분리 + let separatedCharactersCount = separatedCharacters.count // 분리된 자음, 모음의 개수 + + if separatedCharactersCount == 1 && !addedText.isConsonant { + return true + } else if separatedCharactersCount == 2 && addedText.isConsonant { + return true + } else if separatedCharactersCount == 3 && addedText.isConsonant { + return true + } + return false + } + + func textFieldDidChangeSelection(_ textField: UITextField) { + let text = textField.text ?? "" // textField에 수정이 반영된 후의 text + let maxLength = 12 // 글자 수 제한 + if text.count >= maxLength { + let startIndex = text.startIndex + let endIndex = text.index(startIndex, offsetBy: maxLength - 1) + let fixedText = String(text[startIndex...endIndex]) + textField.text = fixedText + self.originView.numOfLetters.text = "12/12" + } else { + self.originView.numOfLetters.text = "\(text.count)/12" + } + } +} diff --git a/DontBe-iOS/DontBe-iOS/Presentation/Join/ViewModels/JoinAgreeViewModel.swift b/DontBe-iOS/DontBe-iOS/Presentation/Join/ViewModels/JoinAgreeViewModel.swift index 7ea625a6..63f6ecdb 100644 --- a/DontBe-iOS/DontBe-iOS/Presentation/Join/ViewModels/JoinAgreeViewModel.swift +++ b/DontBe-iOS/DontBe-iOS/Presentation/Join/ViewModels/JoinAgreeViewModel.swift @@ -15,7 +15,8 @@ final class JoinAgreeViewModel: ViewModelType { private let allButtonChecked = PassthroughSubject() private let isEnabled = PassthroughSubject() private let clickedButtonState = PassthroughSubject<(Int, Bool), Never>() - + private let pushViewController = PassthroughSubject() + private var isAllChecked = false private var isFirstChecked = false private var isSecondChecked = false @@ -29,6 +30,7 @@ final class JoinAgreeViewModel: ViewModelType { let secondCheckButtonTapped: AnyPublisher let thirdCheckButtonTapped: AnyPublisher let fourthCheckButtonTapped: AnyPublisher + let nextButtonTapped: AnyPublisher } struct Output { @@ -36,9 +38,16 @@ final class JoinAgreeViewModel: ViewModelType { let isAllcheck: PassthroughSubject let isEnable: PassthroughSubject let clickedButtonState: PassthroughSubject<(Int, Bool), Never> + let pushViewController: PassthroughSubject } func transform(from input: Input, cancelBag: CancelBag) -> Output { + input.backButtonTapped + .sink { _ in + self.popViewController.send() + } + .store(in: cancelBag) + input.allCheckButtonTapped .sink { [weak self] _ in // 모든 버튼 상태를 업데이트하고 신호를 보냄 @@ -87,18 +96,18 @@ final class JoinAgreeViewModel: ViewModelType { self?.isEnabled.send(self?.isNextButtonEnabled() ?? 0) } .store(in: cancelBag) - - - input.backButtonTapped + + input.nextButtonTapped .sink { _ in - self.popViewController.send() + self.pushViewController.send() } .store(in: cancelBag) return Output(popViewController: popViewController, isAllcheck: allButtonChecked, isEnable: isEnabled, - clickedButtonState: clickedButtonState) + clickedButtonState: clickedButtonState, + pushViewController: pushViewController) } private func isNextButtonEnabled() -> Int { diff --git a/DontBe-iOS/DontBe-iOS/Presentation/Join/ViewModels/JoinProfileViewModel.swift b/DontBe-iOS/DontBe-iOS/Presentation/Join/ViewModels/JoinProfileViewModel.swift new file mode 100644 index 00000000..dbeb75b3 --- /dev/null +++ b/DontBe-iOS/DontBe-iOS/Presentation/Join/ViewModels/JoinProfileViewModel.swift @@ -0,0 +1,55 @@ +// +// JoinProfileViewModel.swift +// DontBe-iOS +// +// Created by 변희주 on 1/11/24. +// + +import Combine +import Foundation + +final class JoinProfileViewModel: ViewModelType { + private let cancelBag = CancelBag() + private let pushOrPopViewController = PassthroughSubject() + private let isNotDuplicated = PassthroughSubject() + private var isPossible: Bool = true + + struct Input { + let backButtonTapped: AnyPublisher + let duplicationCheckButtonTapped: AnyPublisher + let finishButtonTapped: AnyPublisher + } + + struct Output { + let pushOrPopViewController: PassthroughSubject + let isEnable: PassthroughSubject + } + + func transform(from input: Input, cancelBag: CancelBag) -> Output { + input.backButtonTapped + .sink { _ in + self.pushOrPopViewController.send(0) + } + .store(in: self.cancelBag) + + input.duplicationCheckButtonTapped + .sink { value in + print(value) + // value(텍스트 필드의 텍스트) 가지고 서버통신 ㄱㄱ + // 서버통신 완료되면 신호 + /* 서버통신 -> 사용불가 -> false + -> 사용가능 -> true 반환 */ + self.isNotDuplicated.send(self.isPossible) + } + .store(in: self.cancelBag) + + input.finishButtonTapped + .sink { _ in + self.pushOrPopViewController.send(1) + } + .store(in: self.cancelBag) + + return Output(pushOrPopViewController: pushOrPopViewController, + isEnable: isNotDuplicated) + } +} diff --git a/DontBe-iOS/DontBe-iOS/Presentation/Join/Views/JoinProfileView.swift b/DontBe-iOS/DontBe-iOS/Presentation/Join/Views/JoinProfileView.swift new file mode 100644 index 00000000..baa8892b --- /dev/null +++ b/DontBe-iOS/DontBe-iOS/Presentation/Join/Views/JoinProfileView.swift @@ -0,0 +1,187 @@ +// +// ProfileView.swift +// DontBe-iOS +// +// Created by 변희주 on 1/11/24. +// + +import UIKit + +import SnapKit + +final class JoinProfileView: UIView { + + // MARK: - Properties + + // MARK: - UI Components + + private let topDivisionLine = UIView().makeDivisionLine() + + let profileImage: UIImageView = { + let profileImage = UIImageView() + profileImage.setCircularImage(image: ImageLiterals.Common.imgProfile) + return profileImage + }() + + let plusButton: UIButton = { + let plusButton = UIButton() + plusButton.setImage(ImageLiterals.Join.btnPlus, for: .normal) + return plusButton + }() + + private let nickNameLabel: UILabel = { + let nickNameLabel = UILabel() + nickNameLabel.text = StringLiterals.Join.nickName + nickNameLabel.textColor = .donBlack + nickNameLabel.font = .font(.body1) + return nickNameLabel + }() + + let nickNameTextField: UITextField = { + let nickNameTextField = UITextField() + nickNameTextField.placeholder = StringLiterals.Join.nickNamePlaceHolder + nickNameTextField.textAlignment = .left + nickNameTextField.textColor = .donBlack + nickNameTextField.font = .font(.body4) + nickNameTextField.backgroundColor = .donGray2 + nickNameTextField.layer.cornerRadius = 4.adjusted + nickNameTextField.setPlaceholderColor(.donGray7) + nickNameTextField.setLeftPaddingPoints(14.adjusted) + nickNameTextField.setRightPaddingPoints(14.adjusted) + return nickNameTextField + }() + + let numOfLetters: UILabel = { + let label = UILabel() + label.text = "(0/12)" + label.textColor = .donGray7 + label.font = .font(.caption4) + return label + }() + + let duplicationCheckButton: UIButton = { + let duplicationCheckButton = UIButton() + duplicationCheckButton.setTitle(StringLiterals.Join.duplicationCheck, for: .normal) + duplicationCheckButton.setTitleColor(.donBlack, for: .normal) + duplicationCheckButton.titleLabel?.font = .font(.body3) + duplicationCheckButton.layer.cornerRadius = 4.adjusted + duplicationCheckButton.layer.masksToBounds = true + duplicationCheckButton.backgroundColor = .donPrimary + return duplicationCheckButton + }() + + let duplicationCheckDescription: UILabel = { + let duplicationCheckDescription = UILabel() + duplicationCheckDescription.text = StringLiterals.Join.duplicationCheckDescription + duplicationCheckDescription.textColor = .donGray8 + duplicationCheckDescription.font = .font(.caption4) + return duplicationCheckDescription + }() + + let finishButton: UIButton = { + let finishButton = CustomButton(title: StringLiterals.Button.finish, backColor: .donGray4, titleColor: .donGray9) + finishButton.isEnabled = false + return finishButton + }() + + let finishActiveButton: UIButton = { + let finishActiveButton = CustomButton(title: StringLiterals.Button.finish, backColor: .donBlack, titleColor: .donWhite) + finishActiveButton.isHidden = true + return finishActiveButton + }() + + // MARK: - Life Cycles + + override init(frame: CGRect) { + super.init(frame: frame) + + setUI() + setHierarchy() + setLayout() + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +// MARK: - Extensions + +extension JoinProfileView { + func setUI() { + self.backgroundColor = .donWhite + } + + func setHierarchy() { + self.addSubviews(topDivisionLine, + profileImage, + plusButton, + nickNameLabel, + nickNameTextField, + duplicationCheckButton, + duplicationCheckDescription, + finishButton, + finishActiveButton) + + nickNameTextField.addSubview(numOfLetters) + } + + func setLayout() { + topDivisionLine.snp.makeConstraints { + $0.leading.trailing.equalToSuperview() + $0.top.equalTo(self.safeAreaLayoutGuide) + $0.height.equalTo(1.adjusted) + } + + profileImage.snp.makeConstraints { + $0.top.equalTo(self.safeAreaLayoutGuide).inset(52.adjustedH) + $0.centerX.equalToSuperview() + $0.size.equalTo(100.adjusted) + } + + plusButton.snp.makeConstraints { + $0.top.equalTo(profileImage).offset(72.adjusted) + $0.leading.equalTo(profileImage).offset(78.adjusted) + $0.size.equalTo(34.adjusted) + } + + nickNameLabel.snp.makeConstraints { + $0.top.equalTo(self.safeAreaLayoutGuide).inset(171.adjustedH) + $0.leading.equalToSuperview().inset(16.adjusted) + } + + nickNameTextField.snp.makeConstraints { + $0.top.equalTo(nickNameLabel.snp.bottom).offset(10.adjustedH) + $0.leading.equalToSuperview().inset(16.adjusted) + $0.trailing.equalToSuperview().inset(107.adjusted) + $0.height.equalTo(44.adjusted) + } + + numOfLetters.snp.makeConstraints { + $0.trailing.equalToSuperview().inset(14.adjusted) + $0.centerY.equalToSuperview() + } + + duplicationCheckButton.snp.makeConstraints { + $0.centerY.height.equalTo(nickNameTextField) + $0.leading.equalTo(nickNameTextField.snp.trailing).offset(6.adjusted) + $0.trailing.equalToSuperview().inset(16.adjusted) + } + + duplicationCheckDescription.snp.makeConstraints { + $0.top.equalTo(nickNameTextField.snp.bottom).offset(6.adjustedH) + $0.leading.equalToSuperview().inset(16.adjusted) + } + + finishButton.snp.makeConstraints { + $0.bottom.equalTo(self.safeAreaLayoutGuide).inset(29.adjusted) + $0.centerX.equalToSuperview() + } + + finishActiveButton.snp.makeConstraints { + $0.bottom.equalTo(self.safeAreaLayoutGuide).inset(29.adjusted) + $0.centerX.equalToSuperview() + } + } +} diff --git a/DontBe-iOS/DontBe-iOS/Presentation/Onboarding/ViewControllers/OnboardingEndingViewController.swift b/DontBe-iOS/DontBe-iOS/Presentation/Onboarding/ViewControllers/OnboardingEndingViewController.swift index 156da89d..35292ada 100644 --- a/DontBe-iOS/DontBe-iOS/Presentation/Onboarding/ViewControllers/OnboardingEndingViewController.swift +++ b/DontBe-iOS/DontBe-iOS/Presentation/Onboarding/ViewControllers/OnboardingEndingViewController.swift @@ -36,7 +36,7 @@ final class OnboardingEndingViewController: UIViewController { private let profileImage: UIImageView = { let profile = UIImageView() - profile.image = ImageLiterals.Onboarding.imgProfile + profile.image = ImageLiterals.Common.imgProfile return profile }() @@ -148,7 +148,7 @@ extension OnboardingEndingViewController { output.voidPublisher .sink { value in if value == "start" { - let viewController = OnboardingViewController() + let viewController = DontBeTabBarController() print(self.introductionView.introduction.text ?? "") // 텍스트 필드 텍스트 잘 넘어오는지 확인 self.navigationController?.pushViewController(viewController, animated: true) } else { diff --git a/DontBe-iOS/DontBe-iOS/Presentation/Onboarding/Views/IntroductionView.swift b/DontBe-iOS/DontBe-iOS/Presentation/Onboarding/Views/IntroductionView.swift index d70df1fa..7a4f2db7 100644 --- a/DontBe-iOS/DontBe-iOS/Presentation/Onboarding/Views/IntroductionView.swift +++ b/DontBe-iOS/DontBe-iOS/Presentation/Onboarding/Views/IntroductionView.swift @@ -17,7 +17,7 @@ final class IntroductionView: UIView { private let nickName: UILabel = { let nickName = UILabel() - nickName.text = "닉네임" + nickName.text = UserDefaults.standard.string(forKey: "nickname") nickName.textColor = .donBlack nickName.font = .font(.head3) return nickName