diff --git a/34th-SOPT-iOS-DanggeunCloneCoding/.gitignore b/.gitignore similarity index 100% rename from 34th-SOPT-iOS-DanggeunCloneCoding/.gitignore rename to .gitignore diff --git a/34th-SOPT-iOS-DanggeunCloneCoding.xcodeproj/project.pbxproj b/34th-SOPT-iOS-DanggeunCloneCoding.xcodeproj/project.pbxproj index a2a9a3a..9170d57 100644 --- a/34th-SOPT-iOS-DanggeunCloneCoding.xcodeproj/project.pbxproj +++ b/34th-SOPT-iOS-DanggeunCloneCoding.xcodeproj/project.pbxproj @@ -32,6 +32,35 @@ 4E036EA02BD2AF500017A9BF /* Pretendard-ExtraBold.otf in Resources */ = {isa = PBXBuildFile; fileRef = 4E036E952BD2AF0A0017A9BF /* Pretendard-ExtraBold.otf */; }; 4E036EA12BD2AF500017A9BF /* Pretendard-Bold.otf in Resources */ = {isa = PBXBuildFile; fileRef = 4E036E922BD2AF0A0017A9BF /* Pretendard-Bold.otf */; }; 4E036EA22BD2AF500017A9BF /* Pretendard-Light.otf in Resources */ = {isa = PBXBuildFile; fileRef = 4E036E962BD2AF0B0017A9BF /* Pretendard-Light.otf */; }; + 4EAE8F562C0984C50090542F /* LoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EAE8F552C0984C50090542F /* LoginView.swift */; }; + 4EAE8F592C099C5C0090542F /* Toast.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EAE8F582C099C5C0090542F /* Toast.swift */; }; + 4EAE8F5B2C099C830090542F /* ObservablePattern.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EAE8F5A2C099C830090542F /* ObservablePattern.swift */; }; + 4EAE8F5D2C099C9F0090542F /* LoginViewController_MVVM.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EAE8F5C2C099C9F0090542F /* LoginViewController_MVVM.swift */; }; + 4EAE8F5F2C099F200090542F /* LoginViewController_MVVMObservable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EAE8F5E2C099F200090542F /* LoginViewController_MVVMObservable.swift */; }; + 4EAE8F612C099F440090542F /* LoginViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EAE8F602C099F440090542F /* LoginViewModel.swift */; }; + 4EAE8F632C099F6D0090542F /* LoginViewModel_Observable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EAE8F622C099F6D0090542F /* LoginViewModel_Observable.swift */; }; + 4EAE8F692C09A8040090542F /* LoginViewModelType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EAE8F682C09A8040090542F /* LoginViewModelType.swift */; }; + 4EAE8F6B2C09A83E0090542F /* LoginViewModel_DI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EAE8F6A2C09A83E0090542F /* LoginViewModel_DI.swift */; }; + 4ED0589A2C01BD6400F5680E /* LoginViewController_DI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4ED058992C01BD6400F5680E /* LoginViewController_DI.swift */; }; + 4ED0589D2C01BD9B00F5680E /* ViewModelType(input-output Pattern).swift in Sources */ = {isa = PBXBuildFile; fileRef = 4ED0589C2C01BD9B00F5680E /* ViewModelType(input-output Pattern).swift */; }; + 4ED0589F2C01BDBE00F5680E /* LoginViewModel_Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4ED0589E2C01BDBE00F5680E /* LoginViewModel_Rx.swift */; }; + 4ED058A12C01BDE300F5680E /* LoginViewController_Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4ED058A02C01BDE300F5680E /* LoginViewController_Rx.swift */; }; + 4ED058A32C01BE0000F5680E /* LoginViewController_LiveCodingTemplate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4ED058A22C01BE0000F5680E /* LoginViewController_LiveCodingTemplate.swift */; }; + 4ED058A62C01C75400F5680E /* RxCocoa in Frameworks */ = {isa = PBXBuildFile; productRef = 4ED058A52C01C75400F5680E /* RxCocoa */; }; + 4ED058A82C01C75400F5680E /* RxRelay in Frameworks */ = {isa = PBXBuildFile; productRef = 4ED058A72C01C75400F5680E /* RxRelay */; }; + 4ED52D0E2C09E00F0033080E /* LoginViewController_Rx_Generic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4ED52D0D2C09E00F0033080E /* LoginViewController_Rx_Generic.swift */; }; + 4ED9F3D62BDCC52300712737 /* Moya in Frameworks */ = {isa = PBXBuildFile; productRef = 4ED9F3D52BDCC52300712737 /* Moya */; }; + 4ED9F3D82BDCCBD600712737 /* Config.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4ED9F3D72BDCCBD600712737 /* Config.swift */; }; + 4ED9F3DA2BDCCF3800712737 /* NetworkResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4ED9F3D92BDCCF3800712737 /* NetworkResult.swift */; }; + 4ED9F3DC2BDCCF6300712737 /* MoyaLoggingPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4ED9F3DB2BDCCF6300712737 /* MoyaLoggingPlugin.swift */; }; + 4ED9F3DE2BDCCF9800712737 /* SignUpRequestModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4ED9F3DD2BDCCF9800712737 /* SignUpRequestModel.swift */; }; + 4ED9F3E02BDCCFC900712737 /* SignUpResponseModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4ED9F3DF2BDCCFC900712737 /* SignUpResponseModel.swift */; }; + 4ED9F3E22BDCD00200712737 /* SignUpViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4ED9F3E12BDCD00200712737 /* SignUpViewController.swift */; }; + 4ED9F3E52BDCD0CE00712737 /* Then in Frameworks */ = {isa = PBXBuildFile; productRef = 4ED9F3E42BDCD0CE00712737 /* Then */; }; + 4ED9F3E72BDCD0EA00712737 /* UserTargetType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4ED9F3E62BDCD0EA00712737 /* UserTargetType.swift */; }; + 4ED9F3E92BDCD10300712737 /* UserService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4ED9F3E82BDCD10300712737 /* UserService.swift */; }; + 4ED9F3EB2BDCD12500712737 /* CheckUserInfoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4ED9F3EA2BDCD12500712737 /* CheckUserInfoViewController.swift */; }; + 4ED9F3ED2BDCD14000712737 /* UserInfoResponseModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4ED9F3EC2BDCD14000712737 /* UserInfoResponseModel.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -80,6 +109,31 @@ 4E036E972BD2AF0B0017A9BF /* Pretendard-Thin.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Pretendard-Thin.otf"; sourceTree = ""; }; 4E036E982BD2AF0B0017A9BF /* Pretendard-SemiBold.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Pretendard-SemiBold.otf"; sourceTree = ""; }; 4E036E992BD2AF0B0017A9BF /* Pretendard-Regular.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Pretendard-Regular.otf"; sourceTree = ""; }; + 4EAE8F552C0984C50090542F /* LoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginView.swift; sourceTree = ""; }; + 4EAE8F582C099C5C0090542F /* Toast.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Toast.swift; sourceTree = ""; }; + 4EAE8F5A2C099C830090542F /* ObservablePattern.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObservablePattern.swift; sourceTree = ""; }; + 4EAE8F5C2C099C9F0090542F /* LoginViewController_MVVM.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginViewController_MVVM.swift; sourceTree = ""; }; + 4EAE8F5E2C099F200090542F /* LoginViewController_MVVMObservable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginViewController_MVVMObservable.swift; sourceTree = ""; }; + 4EAE8F602C099F440090542F /* LoginViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginViewModel.swift; sourceTree = ""; }; + 4EAE8F622C099F6D0090542F /* LoginViewModel_Observable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginViewModel_Observable.swift; sourceTree = ""; }; + 4EAE8F682C09A8040090542F /* LoginViewModelType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginViewModelType.swift; sourceTree = ""; }; + 4EAE8F6A2C09A83E0090542F /* LoginViewModel_DI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginViewModel_DI.swift; sourceTree = ""; }; + 4ED058992C01BD6400F5680E /* LoginViewController_DI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginViewController_DI.swift; sourceTree = ""; }; + 4ED0589C2C01BD9B00F5680E /* ViewModelType(input-output Pattern).swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ViewModelType(input-output Pattern).swift"; sourceTree = ""; }; + 4ED0589E2C01BDBE00F5680E /* LoginViewModel_Rx.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginViewModel_Rx.swift; sourceTree = ""; }; + 4ED058A02C01BDE300F5680E /* LoginViewController_Rx.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginViewController_Rx.swift; sourceTree = ""; }; + 4ED058A22C01BE0000F5680E /* LoginViewController_LiveCodingTemplate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginViewController_LiveCodingTemplate.swift; sourceTree = ""; }; + 4ED52D0D2C09E00F0033080E /* LoginViewController_Rx_Generic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginViewController_Rx_Generic.swift; sourceTree = ""; }; + 4ED9F3D72BDCCBD600712737 /* Config.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Config.swift; sourceTree = ""; }; + 4ED9F3D92BDCCF3800712737 /* NetworkResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkResult.swift; sourceTree = ""; }; + 4ED9F3DB2BDCCF6300712737 /* MoyaLoggingPlugin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoyaLoggingPlugin.swift; sourceTree = ""; }; + 4ED9F3DD2BDCCF9800712737 /* SignUpRequestModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignUpRequestModel.swift; sourceTree = ""; }; + 4ED9F3DF2BDCCFC900712737 /* SignUpResponseModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignUpResponseModel.swift; sourceTree = ""; }; + 4ED9F3E12BDCD00200712737 /* SignUpViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignUpViewController.swift; sourceTree = ""; }; + 4ED9F3E62BDCD0EA00712737 /* UserTargetType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserTargetType.swift; sourceTree = ""; }; + 4ED9F3E82BDCD10300712737 /* UserService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserService.swift; sourceTree = ""; }; + 4ED9F3EA2BDCD12500712737 /* CheckUserInfoViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckUserInfoViewController.swift; sourceTree = ""; }; + 4ED9F3EC2BDCD14000712737 /* UserInfoResponseModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserInfoResponseModel.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -87,6 +141,10 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 4ED058A82C01C75400F5680E /* RxRelay in Frameworks */, + 4ED9F3D62BDCC52300712737 /* Moya in Frameworks */, + 4ED9F3E52BDCD0CE00712737 /* Then in Frameworks */, + 4ED058A62C01C75400F5680E /* RxCocoa in Frameworks */, 4E036E8F2BD2AEEF0017A9BF /* SnapKit in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -111,6 +169,7 @@ 4E036E0E2BD23CA80017A9BF = { isa = PBXGroup; children = ( + 4E036E7F2BD2AC640017A9BF /* .gitignore */, 4E036E192BD23CA80017A9BF /* 34th-SOPT-iOS-DanggeunCloneCoding */, 4E036E302BD23CAA0017A9BF /* 34th-SOPT-iOS-DanggeunCloneCodingTests */, 4E036E3A2BD23CAA0017A9BF /* 34th-SOPT-iOS-DanggeunCloneCodingUITests */, @@ -131,20 +190,26 @@ 4E036E192BD23CA80017A9BF /* 34th-SOPT-iOS-DanggeunCloneCoding */ = { isa = PBXGroup; children = ( - 4E036E7F2BD2AC640017A9BF /* .gitignore */, + 4EAE8F642C09A0A80090542F /* Global */, + 4EAE8F572C099C400090542F /* 6th-Seminar */, + 4ED058982C01BD4600F5680E /* 7th-Seminar */, + 4EAE8F512C0982FA0090542F /* LoginView */, 4E036E902BD2AEF90017A9BF /* PretendardFont */, - 4E036E1A2BD23CA80017A9BF /* AppDelegate.swift */, - 4E036E1C2BD23CA80017A9BF /* SceneDelegate.swift */, - 4E036E892BD2AEA40017A9BF /* LoginViewController.swift */, 4E036E8B2BD2AEC00017A9BF /* ColorAssets.swift */, 4E036E1E2BD23CA80017A9BF /* ViewController.swift */, 4E036E812BD2AE240017A9BF /* WelcomeViewController.swift */, - 4E036E832BD2AE460017A9BF /* UITextFieldExtensions.swift */, - 4E036E852BD2AE600017A9BF /* UIFontExtension.swift */, 4E036E872BD2AE740017A9BF /* ScrollViewController.swift */, 4E036E232BD23CAA0017A9BF /* Assets.xcassets */, 4E036E252BD23CAA0017A9BF /* LaunchScreen.storyboard */, 4E036E282BD23CAA0017A9BF /* Info.plist */, + 4ED9F3EE2BDCD7BF00712737 /* Networking */, + 4ED9F3D72BDCCBD600712737 /* Config.swift */, + 4ED9F3D92BDCCF3800712737 /* NetworkResult.swift */, + 4ED9F3DB2BDCCF6300712737 /* MoyaLoggingPlugin.swift */, + 4ED9F3E12BDCD00200712737 /* SignUpViewController.swift */, + 4ED9F3E62BDCD0EA00712737 /* UserTargetType.swift */, + 4ED9F3E82BDCD10300712737 /* UserService.swift */, + 4ED9F3EA2BDCD12500712737 /* CheckUserInfoViewController.swift */, ); path = "34th-SOPT-iOS-DanggeunCloneCoding"; sourceTree = ""; @@ -182,6 +247,136 @@ path = PretendardFont; sourceTree = ""; }; + 4EAE8F512C0982FA0090542F /* LoginView */ = { + isa = PBXGroup; + children = ( + 4EAE8F542C09832B0090542F /* Views */, + 4EAE8F532C0983100090542F /* ViewControllers */, + ); + path = LoginView; + sourceTree = ""; + }; + 4EAE8F532C0983100090542F /* ViewControllers */ = { + isa = PBXGroup; + children = ( + 4E036E892BD2AEA40017A9BF /* LoginViewController.swift */, + ); + path = ViewControllers; + sourceTree = ""; + }; + 4EAE8F542C09832B0090542F /* Views */ = { + isa = PBXGroup; + children = ( + 4EAE8F552C0984C50090542F /* LoginView.swift */, + ); + path = Views; + sourceTree = ""; + }; + 4EAE8F572C099C400090542F /* 6th-Seminar */ = { + isa = PBXGroup; + children = ( + 4EAE8F672C09A5380090542F /* withObservablePattern */, + 4EAE8F662C09A5260090542F /* withoutObservablePattern */, + ); + path = "6th-Seminar"; + sourceTree = ""; + }; + 4EAE8F642C09A0A80090542F /* Global */ = { + isa = PBXGroup; + children = ( + 4EAE8F6F2C09B7270090542F /* Application */, + 4EAE8F5A2C099C830090542F /* ObservablePattern.swift */, + 4EAE8F582C099C5C0090542F /* Toast.swift */, + 4EAE8F652C09A0B00090542F /* Extensions */, + ); + path = Global; + sourceTree = ""; + }; + 4EAE8F652C09A0B00090542F /* Extensions */ = { + isa = PBXGroup; + children = ( + 4E036E832BD2AE460017A9BF /* UITextFieldExtensions.swift */, + 4E036E852BD2AE600017A9BF /* UIFontExtension.swift */, + ); + path = Extensions; + sourceTree = ""; + }; + 4EAE8F662C09A5260090542F /* withoutObservablePattern */ = { + isa = PBXGroup; + children = ( + 4EAE8F5C2C099C9F0090542F /* LoginViewController_MVVM.swift */, + 4EAE8F602C099F440090542F /* LoginViewModel.swift */, + ); + path = withoutObservablePattern; + sourceTree = ""; + }; + 4EAE8F672C09A5380090542F /* withObservablePattern */ = { + isa = PBXGroup; + children = ( + 4EAE8F5E2C099F200090542F /* LoginViewController_MVVMObservable.swift */, + 4EAE8F622C099F6D0090542F /* LoginViewModel_Observable.swift */, + ); + path = withObservablePattern; + sourceTree = ""; + }; + 4EAE8F6C2C09A9580090542F /* DI */ = { + isa = PBXGroup; + children = ( + 4EAE8F682C09A8040090542F /* LoginViewModelType.swift */, + 4EAE8F6A2C09A83E0090542F /* LoginViewModel_DI.swift */, + 4ED058992C01BD6400F5680E /* LoginViewController_DI.swift */, + ); + path = DI; + sourceTree = ""; + }; + 4EAE8F6D2C09A9600090542F /* DI + Rx + input-output Pattern */ = { + isa = PBXGroup; + children = ( + 4ED0589C2C01BD9B00F5680E /* ViewModelType(input-output Pattern).swift */, + 4ED0589E2C01BDBE00F5680E /* LoginViewModel_Rx.swift */, + 4ED058A02C01BDE300F5680E /* LoginViewController_Rx.swift */, + 4ED52D0D2C09E00F0033080E /* LoginViewController_Rx_Generic.swift */, + ); + path = "DI + Rx + input-output Pattern"; + sourceTree = ""; + }; + 4EAE8F6F2C09B7270090542F /* Application */ = { + isa = PBXGroup; + children = ( + 4E036E1A2BD23CA80017A9BF /* AppDelegate.swift */, + 4E036E1C2BD23CA80017A9BF /* SceneDelegate.swift */, + ); + path = Application; + sourceTree = ""; + }; + 4ED058982C01BD4600F5680E /* 7th-Seminar */ = { + isa = PBXGroup; + children = ( + 4ED058A22C01BE0000F5680E /* LoginViewController_LiveCodingTemplate.swift */, + 4EAE8F6C2C09A9580090542F /* DI */, + 4EAE8F6D2C09A9600090542F /* DI + Rx + input-output Pattern */, + ); + path = "7th-Seminar"; + sourceTree = ""; + }; + 4ED9F3EE2BDCD7BF00712737 /* Networking */ = { + isa = PBXGroup; + children = ( + 4ED9F3EF2BDCD7C800712737 /* Model */, + ); + path = Networking; + sourceTree = ""; + }; + 4ED9F3EF2BDCD7C800712737 /* Model */ = { + isa = PBXGroup; + children = ( + 4ED9F3DD2BDCCF9800712737 /* SignUpRequestModel.swift */, + 4ED9F3DF2BDCCFC900712737 /* SignUpResponseModel.swift */, + 4ED9F3EC2BDCD14000712737 /* UserInfoResponseModel.swift */, + ); + path = Model; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -200,6 +395,10 @@ name = "34th-SOPT-iOS-DanggeunCloneCoding"; packageProductDependencies = ( 4E036E8E2BD2AEEF0017A9BF /* SnapKit */, + 4ED9F3D52BDCC52300712737 /* Moya */, + 4ED9F3E42BDCD0CE00712737 /* Then */, + 4ED058A52C01C75400F5680E /* RxCocoa */, + 4ED058A72C01C75400F5680E /* RxRelay */, ); productName = "34th-SOPT-iOS-DanggeunCloneCoding"; productReference = 4E036E172BD23CA80017A9BF /* 34th-SOPT-iOS-DanggeunCloneCoding.app */; @@ -275,6 +474,9 @@ mainGroup = 4E036E0E2BD23CA80017A9BF; packageReferences = ( 4E036E8D2BD2AEEF0017A9BF /* XCRemoteSwiftPackageReference "SnapKit" */, + 4ED9F3D42BDCC52300712737 /* XCRemoteSwiftPackageReference "Moya" */, + 4ED9F3E32BDCD0CE00712737 /* XCRemoteSwiftPackageReference "Then" */, + 4ED058A42C01C75400F5680E /* XCRemoteSwiftPackageReference "RxSwift" */, ); productRefGroup = 4E036E182BD23CA80017A9BF /* Products */; projectDirPath = ""; @@ -328,15 +530,40 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 4ED9F3DE2BDCCF9800712737 /* SignUpRequestModel.swift in Sources */, 4E036E1F2BD23CA80017A9BF /* ViewController.swift in Sources */, + 4EAE8F5F2C099F200090542F /* LoginViewController_MVVMObservable.swift in Sources */, 4E036E822BD2AE240017A9BF /* WelcomeViewController.swift in Sources */, + 4ED9F3DA2BDCCF3800712737 /* NetworkResult.swift in Sources */, + 4EAE8F632C099F6D0090542F /* LoginViewModel_Observable.swift in Sources */, + 4ED058A12C01BDE300F5680E /* LoginViewController_Rx.swift in Sources */, + 4EAE8F6B2C09A83E0090542F /* LoginViewModel_DI.swift in Sources */, + 4ED0589F2C01BDBE00F5680E /* LoginViewModel_Rx.swift in Sources */, + 4ED9F3E02BDCCFC900712737 /* SignUpResponseModel.swift in Sources */, + 4EAE8F612C099F440090542F /* LoginViewModel.swift in Sources */, 4E036E842BD2AE460017A9BF /* UITextFieldExtensions.swift in Sources */, + 4ED9F3E92BDCD10300712737 /* UserService.swift in Sources */, 4E036E882BD2AE740017A9BF /* ScrollViewController.swift in Sources */, + 4ED9F3E72BDCD0EA00712737 /* UserTargetType.swift in Sources */, 4E036E8A2BD2AEA40017A9BF /* LoginViewController.swift in Sources */, + 4ED9F3DC2BDCCF6300712737 /* MoyaLoggingPlugin.swift in Sources */, + 4EAE8F592C099C5C0090542F /* Toast.swift in Sources */, + 4EAE8F562C0984C50090542F /* LoginView.swift in Sources */, + 4ED0589A2C01BD6400F5680E /* LoginViewController_DI.swift in Sources */, + 4EAE8F5B2C099C830090542F /* ObservablePattern.swift in Sources */, + 4ED9F3E22BDCD00200712737 /* SignUpViewController.swift in Sources */, + 4EAE8F5D2C099C9F0090542F /* LoginViewController_MVVM.swift in Sources */, 4E036E1B2BD23CA80017A9BF /* AppDelegate.swift in Sources */, 4E036E8C2BD2AEC00017A9BF /* ColorAssets.swift in Sources */, + 4ED9F3EB2BDCD12500712737 /* CheckUserInfoViewController.swift in Sources */, + 4ED9F3D82BDCCBD600712737 /* Config.swift in Sources */, + 4ED52D0E2C09E00F0033080E /* LoginViewController_Rx_Generic.swift in Sources */, + 4EAE8F692C09A8040090542F /* LoginViewModelType.swift in Sources */, 4E036E862BD2AE600017A9BF /* UIFontExtension.swift in Sources */, 4E036E1D2BD23CA80017A9BF /* SceneDelegate.swift in Sources */, + 4ED9F3ED2BDCD14000712737 /* UserInfoResponseModel.swift in Sources */, + 4ED058A32C01BE0000F5680E /* LoginViewController_LiveCodingTemplate.swift in Sources */, + 4ED0589D2C01BD9B00F5680E /* ViewModelType(input-output Pattern).swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -508,6 +735,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + BASE_URL = "http://34.64.233.12:8080"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = 548W892M42; @@ -535,6 +763,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + BASE_URL = "http://34.64.233.12:8080"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = 548W892M42; @@ -683,6 +912,30 @@ minimumVersion = 5.7.1; }; }; + 4ED058A42C01C75400F5680E /* XCRemoteSwiftPackageReference "RxSwift" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/ReactiveX/RxSwift"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 6.7.1; + }; + }; + 4ED9F3D42BDCC52300712737 /* XCRemoteSwiftPackageReference "Moya" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/Moya/Moya"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 15.0.3; + }; + }; + 4ED9F3E32BDCD0CE00712737 /* XCRemoteSwiftPackageReference "Then" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/devxoul/Then"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 3.0.0; + }; + }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ @@ -691,6 +944,26 @@ package = 4E036E8D2BD2AEEF0017A9BF /* XCRemoteSwiftPackageReference "SnapKit" */; productName = SnapKit; }; + 4ED058A52C01C75400F5680E /* RxCocoa */ = { + isa = XCSwiftPackageProductDependency; + package = 4ED058A42C01C75400F5680E /* XCRemoteSwiftPackageReference "RxSwift" */; + productName = RxCocoa; + }; + 4ED058A72C01C75400F5680E /* RxRelay */ = { + isa = XCSwiftPackageProductDependency; + package = 4ED058A42C01C75400F5680E /* XCRemoteSwiftPackageReference "RxSwift" */; + productName = RxRelay; + }; + 4ED9F3D52BDCC52300712737 /* Moya */ = { + isa = XCSwiftPackageProductDependency; + package = 4ED9F3D42BDCC52300712737 /* XCRemoteSwiftPackageReference "Moya" */; + productName = Moya; + }; + 4ED9F3E42BDCD0CE00712737 /* Then */ = { + isa = XCSwiftPackageProductDependency; + package = 4ED9F3E32BDCD0CE00712737 /* XCRemoteSwiftPackageReference "Then" */; + productName = Then; + }; /* End XCSwiftPackageProductDependency section */ }; rootObject = 4E036E0F2BD23CA80017A9BF /* Project object */; diff --git a/34th-SOPT-iOS-DanggeunCloneCoding.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/34th-SOPT-iOS-DanggeunCloneCoding.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index d445175..fa8ce28 100644 --- a/34th-SOPT-iOS-DanggeunCloneCoding.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/34th-SOPT-iOS-DanggeunCloneCoding.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,41 @@ { "pins" : [ + { + "identity" : "alamofire", + "kind" : "remoteSourceControl", + "location" : "https://github.com/Alamofire/Alamofire.git", + "state" : { + "revision" : "f455c2975872ccd2d9c81594c658af65716e9b9a", + "version" : "5.9.1" + } + }, + { + "identity" : "moya", + "kind" : "remoteSourceControl", + "location" : "https://github.com/Moya/Moya", + "state" : { + "revision" : "c263811c1f3dbf002be9bd83107f7cdc38992b26", + "version" : "15.0.3" + } + }, + { + "identity" : "reactiveswift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/ReactiveCocoa/ReactiveSwift.git", + "state" : { + "revision" : "c43bae3dac73fdd3cb906bd5a1914686ca71ed3c", + "version" : "6.7.0" + } + }, + { + "identity" : "rxswift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/ReactiveX/RxSwift.git", + "state" : { + "revision" : "b06a8c8596e4c3e8e7788e08e720e3248563ce6a", + "version" : "6.7.1" + } + }, { "identity" : "snapkit", "kind" : "remoteSourceControl", @@ -8,6 +44,15 @@ "revision" : "2842e6e84e82eb9a8dac0100ca90d9444b0307f4", "version" : "5.7.1" } + }, + { + "identity" : "then", + "kind" : "remoteSourceControl", + "location" : "https://github.com/devxoul/Then", + "state" : { + "revision" : "d41ef523faef0f911369f79c0b96815d9dbb6d7a", + "version" : "3.0.0" + } } ], "version" : 2 diff --git a/34th-SOPT-iOS-DanggeunCloneCoding.xcodeproj/xcuserdata/kimminsung.xcuserdatad/xcschemes/xcschememanagement.plist b/34th-SOPT-iOS-DanggeunCloneCoding.xcodeproj/xcuserdata/kimminsung.xcuserdatad/xcschemes/xcschememanagement.plist index 7f08b10..db4a1ef 100644 --- a/34th-SOPT-iOS-DanggeunCloneCoding.xcodeproj/xcuserdata/kimminsung.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/34th-SOPT-iOS-DanggeunCloneCoding.xcodeproj/xcuserdata/kimminsung.xcuserdatad/xcschemes/xcschememanagement.plist @@ -9,27 +9,174 @@ orderHint 0 - SnapKitPlayground (Playground) 1.xcscheme + ReactiveSwift (Playground) 1.xcscheme + + isShown + + orderHint + 5 + + ReactiveSwift (Playground) 2.xcscheme + + isShown + + orderHint + 6 + + ReactiveSwift (Playground) 3.xcscheme + + isShown + + orderHint + 22 + + ReactiveSwift (Playground) 4.xcscheme + + isShown + + orderHint + 23 + + ReactiveSwift (Playground) 5.xcscheme + + isShown + + orderHint + 24 + + ReactiveSwift (Playground).xcscheme + + isShown + + orderHint + 4 + + ReactiveSwift-UIExamples (Playground) 1.xcscheme isShown orderHint 2 - SnapKitPlayground (Playground) 2.xcscheme + ReactiveSwift-UIExamples (Playground) 2.xcscheme isShown orderHint 3 - SnapKitPlayground (Playground).xcscheme + ReactiveSwift-UIExamples (Playground) 3.xcscheme + + isShown + + orderHint + 19 + + ReactiveSwift-UIExamples (Playground) 4.xcscheme + + isShown + + orderHint + 20 + + ReactiveSwift-UIExamples (Playground) 5.xcscheme + + isShown + + orderHint + 21 + + ReactiveSwift-UIExamples (Playground).xcscheme isShown orderHint 0 + Rx (Playground) 1.xcscheme + + isShown + + orderHint + 12 + + Rx (Playground) 2.xcscheme + + isShown + + orderHint + 13 + + Rx (Playground) 3.xcscheme + + isShown + + orderHint + 10 + + Rx (Playground) 4.xcscheme + + isShown + + orderHint + 14 + + Rx (Playground) 5.xcscheme + + isShown + + orderHint + 15 + + Rx (Playground).xcscheme + + isShown + + orderHint + 11 + + SnapKitPlayground (Playground) 1.xcscheme + + isShown + + orderHint + 8 + + SnapKitPlayground (Playground) 2.xcscheme + + isShown + + orderHint + 9 + + SnapKitPlayground (Playground) 3.xcscheme + + isShown + + orderHint + 16 + + SnapKitPlayground (Playground) 4.xcscheme + + isShown + + orderHint + 17 + + SnapKitPlayground (Playground) 5.xcscheme + + isShown + + orderHint + 18 + + SnapKitPlayground (Playground).xcscheme + + isShown + + orderHint + 7 + diff --git a/34th-SOPT-iOS-DanggeunCloneCoding/6th-Seminar/withObservablePattern/LoginViewController_MVVMObservable.swift b/34th-SOPT-iOS-DanggeunCloneCoding/6th-Seminar/withObservablePattern/LoginViewController_MVVMObservable.swift new file mode 100644 index 0000000..694fc1d --- /dev/null +++ b/34th-SOPT-iOS-DanggeunCloneCoding/6th-Seminar/withObservablePattern/LoginViewController_MVVMObservable.swift @@ -0,0 +1,68 @@ +// +// LoginViewController_MVVMObservable.swift +// 34th-SOPT-iOS-DanggeunCloneCoding +// +// Created by 김민성 on 2024/05/31. +// + +import UIKit +import SnapKit + +final class LoginViewController_MVVMObservable: UIViewController { + + private let rootView = LoginView() + private let viewModel = LoginViewModel_Observable() + + override func loadView() { + self.view = rootView + } + + override func viewDidLoad() { + super.viewDidLoad() + + self.view.backgroundColor = .white + + setTarget() + bindViewModel() + } + + private func setTarget() { + rootView.loginButton.addTarget(self, action: #selector(loginButtonDidTap), for: .touchUpInside) + } + + private func bindViewModel() { + viewModel.isValid.bind { [weak self] isValid in + guard let isValid else { return } + if isValid { self?.pushToWelcomeVC() } + } + + viewModel.errMessage.bind { [weak self] err in + guard let err else { return } + self?.showToast(err) + } + } + + + @objc private func loginButtonDidTap() { + viewModel.checkValid( + id: rootView.idTextField.text, + password: rootView.passwordTextField.text + ) + } + + private func pushToWelcomeVC() { + let welcomeViewController = WelcomeViewController() + self.navigationController?.pushViewController(welcomeViewController, animated: true) + } + + func showToast(_ message: String, + bottomInset: CGFloat = 86 + ) { + guard let view else { return } + Toast().show(message: message, + view: view, + bottomInset: bottomInset + ) + } +} + diff --git a/34th-SOPT-iOS-DanggeunCloneCoding/6th-Seminar/withObservablePattern/LoginViewModel_Observable.swift b/34th-SOPT-iOS-DanggeunCloneCoding/6th-Seminar/withObservablePattern/LoginViewModel_Observable.swift new file mode 100644 index 0000000..95058f4 --- /dev/null +++ b/34th-SOPT-iOS-DanggeunCloneCoding/6th-Seminar/withObservablePattern/LoginViewModel_Observable.swift @@ -0,0 +1,39 @@ +// +// LoginViewModel_Observable.swift +// 34th-SOPT-iOS-DanggeunCloneCoding +// +// Created by 김민성 on 2024/05/31. +// + +import UIKit + +final class LoginViewModel_Observable { + var isValid: ObservablePattern = ObservablePattern.init(false) + var errMessage: ObservablePattern = ObservablePattern.init(nil) + + func checkValid(id: String?, password: String?) { + guard let id else { + errMessage.value = "아이디가 비어있습니다." + return + } + guard let password else { + errMessage.value = "비밀번호가 비어있습니다." + return + } + + let idRegEx = "[A-Za-z0-9]{5,13}" + let pwRegEx = "[A-Za-z0-9!_@$%^&+=]{8,20}" + + guard let _ = id.range(of: idRegEx, options: .regularExpression) else { + errMessage.value = "아이디가 유효하지 않습니다." + return + } + + guard let _ = password.range(of: pwRegEx, options: .regularExpression) else { + errMessage.value = "비밀번호가 유효하지 않습니다." + return + } + isValid.value = true + + } +} diff --git a/34th-SOPT-iOS-DanggeunCloneCoding/6th-Seminar/withoutObservablePattern/LoginViewController_MVVM.swift b/34th-SOPT-iOS-DanggeunCloneCoding/6th-Seminar/withoutObservablePattern/LoginViewController_MVVM.swift new file mode 100644 index 0000000..687e901 --- /dev/null +++ b/34th-SOPT-iOS-DanggeunCloneCoding/6th-Seminar/withoutObservablePattern/LoginViewController_MVVM.swift @@ -0,0 +1,69 @@ +// +// LoginViewController_MVVM.swift +// 34th-SOPT-iOS-DanggeunCloneCoding +// +// Created by 김민성 on 2024/05/31. +// + +import UIKit +import SnapKit + +final class LoginViewController_MVVM: UIViewController { + + private let rootView = LoginView() + private let viewModel = LoginViewModel() + + override func loadView() { + self.view = rootView + } + + override func viewDidLoad() { + super.viewDidLoad() + + self.view.backgroundColor = .white + + setTarget() + bindViewModel() + } + + private func setTarget() { + rootView.loginButton.addTarget(self, action: #selector(loginButtonDidTap), for: .touchUpInside) + } + + private func bindViewModel() { + viewModel.isValid = { [weak self] isValid in + if isValid { + self?.pushToWelcomeVC() + } + } + + viewModel.errMessage = { [weak self] err in + if let err = err { + self?.showToast(err) + } + } + } + + + @objc private func loginButtonDidTap() { + viewModel.checkValid( + id: rootView.idTextField.text, + password: rootView.passwordTextField.text + ) + } + + private func pushToWelcomeVC() { + let welcomeViewController = WelcomeViewController() + self.navigationController?.pushViewController(welcomeViewController, animated: true) + } + + func showToast(_ message: String, + bottomInset: CGFloat = 86 + ) { + guard let view else { return } + Toast().show(message: message, + view: view, + bottomInset: bottomInset + ) + } +} diff --git a/34th-SOPT-iOS-DanggeunCloneCoding/6th-Seminar/withoutObservablePattern/LoginViewModel.swift b/34th-SOPT-iOS-DanggeunCloneCoding/6th-Seminar/withoutObservablePattern/LoginViewModel.swift new file mode 100644 index 0000000..eb7ee44 --- /dev/null +++ b/34th-SOPT-iOS-DanggeunCloneCoding/6th-Seminar/withoutObservablePattern/LoginViewModel.swift @@ -0,0 +1,42 @@ +// +// LoginViewModel.swift +// 34th-SOPT-iOS-DanggeunCloneCoding +// +// Created by 김민성 on 2024/05/31. +// + +import UIKit + +final class LoginViewModel { + // 클로저 타입의 프로퍼티 선언 + var isValid: ((Bool) -> Void)? + var errMessage: ((String?) -> Void)? + + func checkValid(id: String?, password: String?) { + guard let id = id, !id.isEmpty else { + errMessage?("아이디가 비어있습니다.") + return + } + guard let password = password, !password.isEmpty else { + errMessage?("비밀번호가 비어있습니다.") + return + } + + let idRegEx = "[A-Za-z0-9]{5,13}" + let pwRegEx = "[A-Za-z0-9!_@$%^&+=]{8,20}" + + if id.range(of: idRegEx, options: .regularExpression) == nil { + errMessage?("아이디가 유효하지 않습니다.") + return + } + + if password.range(of: pwRegEx, options: .regularExpression) == nil { + errMessage?("비밀번호가 유효하지 않습니다.") + return + } + + isValid?(true) + } +} + + diff --git a/34th-SOPT-iOS-DanggeunCloneCoding/7th-Seminar/DI + Rx + input-output Pattern/LoginViewController_Rx.swift b/34th-SOPT-iOS-DanggeunCloneCoding/7th-Seminar/DI + Rx + input-output Pattern/LoginViewController_Rx.swift new file mode 100644 index 0000000..65d80df --- /dev/null +++ b/34th-SOPT-iOS-DanggeunCloneCoding/7th-Seminar/DI + Rx + input-output Pattern/LoginViewController_Rx.swift @@ -0,0 +1,95 @@ +// +// LoginViewController_Rx.swift +// 34th-SOPT-iOS-DanggeunCloneCoding +// +// Created by 김민성 on 2024/05/25. +// + +import UIKit +import SnapKit + +import RxSwift +import RxCocoa + +final class LoginViewController_Rx: UIViewController { + + private let rootView = LoginView() + private let viewModel: LoginViewModel_Rx + + private let disposeBag = DisposeBag() + + override func loadView() { + self.view = rootView + } + + init(viewModel: LoginViewModel_Rx) { + self.viewModel = viewModel + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + + self.view.backgroundColor = .white + self.bindViewModel() + } + + /* viewModel의 결과를 받아 처리하는 코드는 이 bindViewModel 함수 안에서 구현. */ + private func bindViewModel() { + + /* + 이 코드 안적으면 아래에서 에러뜨면서 차라리 제네릭 타입을 쓰라고 경고를 준다. + 그래서 에러를 지우려고 일단 bindViewModel 안에서 강제 타입캐스팅하긴 했는데 + 이러면 의존성 주입한 의미가 없는 것 같아보임... -> 차라리 제네릭을 써 보자! + */ + let viewModel = self.viewModel //as! LoginViewModel_Rx + + /* + viewModel의 transform 메서드의 매개변수로 들어갈 Input 타입의 인스턴스를 view controller에서 생성. + 여기서 생성한 Input 타입의 인스턴스를 이용해 output 인스턴스를 구한다. (viewModel의 transform 메서드의 반환값) + */ + let input = LoginViewModel_Rx.Input.init( + idTextFieldTextObservable: rootView.idTextField.rx.text.asObservable(), + passwordTextFieldTextObservable: rootView.passwordTextField.rx.text.asObservable(), + loginButtonTapObservable: rootView.loginButton.rx.tap.asObservable() + ) + + /* 앞서 구한 Input 인스턴스를 이용해 Output 인스턴스를 구하기 */ + /* + 위의 viewModel에서 타입캐스팅을 하지 않으면 아래 코드에서 에러가 난다. + 대충 'any ViewModelType' 타입인 값( == viewModel)의 멤버 'transform' 메서드를 사용할 수 없다는 이야기인데, + 이는 + */ + let output = viewModel.transform(from: input, disposeBag: disposeBag) + + /* + Output 인스턴스의 속성들을 구독하는 코드 작성 + -> viewModel 의 output은 viewController에서 동작을 구현 + Input 인스턴스의 속성들은 viewModel 내의 transform 메서드 내에서 이루어짐. + */ + output.isValid.subscribe(onNext: { _ in + print(#function) + self.pushToWelcomeVC() + }).disposed(by: disposeBag) + + output.errMessage.subscribe(onNext: { errMessage in + self.showToast(errMessage) + }).disposed(by: disposeBag) + } + + private func pushToWelcomeVC() { + let welcomeViewController = WelcomeViewController() + self.navigationController?.pushViewController(welcomeViewController, animated: true) + } + + func showToast(_ message: String, bottomInset: CGFloat = 86) { + guard let view else { return } + Toast().show(message: message, view: view, bottomInset: bottomInset) + } +} + + diff --git a/34th-SOPT-iOS-DanggeunCloneCoding/7th-Seminar/DI + Rx + input-output Pattern/LoginViewController_Rx_Generic.swift b/34th-SOPT-iOS-DanggeunCloneCoding/7th-Seminar/DI + Rx + input-output Pattern/LoginViewController_Rx_Generic.swift new file mode 100644 index 0000000..13f3383 --- /dev/null +++ b/34th-SOPT-iOS-DanggeunCloneCoding/7th-Seminar/DI + Rx + input-output Pattern/LoginViewController_Rx_Generic.swift @@ -0,0 +1,82 @@ +// +// LoginViewController_Rx(Generic).swift +// 34th-SOPT-iOS-DanggeunCloneCoding +// +// Created by 김민성 on 2024/05/31. +// + +import UIKit +import SnapKit + +import RxSwift +import RxCocoa + +final class LoginViewController_Rx_Generic: UIViewController { + + private let rootView = LoginView() + private let viewModel: LoginViewModel_Rx + + private let disposeBag = DisposeBag() + + override func loadView() { + self.view = rootView + } + + init(viewModel: LoginViewModel_Rx) { + self.viewModel = viewModel + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + + self.view.backgroundColor = .white + self.bindViewModel() + } + + /* viewModel의 결과를 받아 처리하는 코드는 이 bindViewModel 함수 안에서 구현. */ + private func bindViewModel() { + + /* + viewModel의 transform 메서드의 매개변수로 들어갈 Input 타입의 인스턴스를 view controller에서 생성. + 여기서 생성한 Input 타입의 인스턴스를 이용해 output 인스턴스를 구한다. (viewModel의 transform 메서드의 반환값) + */ + let input = LoginViewModel_Rx.Input.init( + idTextFieldTextObservable: rootView.idTextField.rx.text.asObservable(), + passwordTextFieldTextObservable: rootView.passwordTextField.rx.text.asObservable(), + loginButtonTapObservable: rootView.loginButton.rx.tap.asObservable() + )// as! T.Input + + let output = self.viewModel.transform(from: input, disposeBag: disposeBag) + + /* + Output 인스턴스의 속성들을 구독하는 코드 작성 + -> viewModel 의 output은 viewController에서 동작을 구현 + Input 인스턴스의 속성들은 viewModel 내의 transform 메서드 내에서 이루어짐. + */ + output.isValid.subscribe(onNext: { _ in + print(#function) + self.pushToWelcomeVC() + }).disposed(by: disposeBag) + + output.errMessage.subscribe(onNext: { errMessage in + self.showToast(errMessage) + }).disposed(by: disposeBag) + } + + private func pushToWelcomeVC() { + let welcomeViewController = WelcomeViewController() + self.navigationController?.pushViewController(welcomeViewController, animated: true) + } + + func showToast(_ message: String, bottomInset: CGFloat = 86) { + guard let view else { return } + Toast().show(message: message, view: view, bottomInset: bottomInset) + } +} + + diff --git a/34th-SOPT-iOS-DanggeunCloneCoding/7th-Seminar/DI + Rx + input-output Pattern/LoginViewModel_Rx.swift b/34th-SOPT-iOS-DanggeunCloneCoding/7th-Seminar/DI + Rx + input-output Pattern/LoginViewModel_Rx.swift new file mode 100644 index 0000000..eb17758 --- /dev/null +++ b/34th-SOPT-iOS-DanggeunCloneCoding/7th-Seminar/DI + Rx + input-output Pattern/LoginViewModel_Rx.swift @@ -0,0 +1,71 @@ +// +// LoginViewModel_Rx.swift +// 34th-Sopt-Seminar +// +// Created by 류희재 on 5/19/24. +// + +import RxSwift +import RxRelay + +final class LoginViewModel_Rx: ViewModelType { + + private var idText: String? + private var passwordText: String? + + struct Input { + let idTextFieldTextObservable: Observable + let passwordTextFieldTextObservable: Observable + let loginButtonTapObservable: Observable + } + + struct Output { + + /* + isValid와 errMessage는 output으로서, 초깃값을 가지지 않아도 된다. -> PublishSubject 또는 PublishRelay로 구현 + */ + var isValid = PublishRelay() + var errMessage = PublishRelay() + } + + func transform(from input: Input, disposeBag: DisposeBag) -> Output { + + let output = Output() + + input.idTextFieldTextObservable + .subscribe(onNext: { [weak self] id in self?.idText = id }) + .disposed(by: disposeBag) + + input.passwordTextFieldTextObservable + .subscribe(onNext: { [weak self] password in self?.passwordText = password }) + .disposed(by: disposeBag) + + input.loginButtonTapObservable + .subscribe(onNext: { [weak self] _ in + guard let id = self?.idText else { + output.errMessage.accept("아이디가 비어있습니다.") + return + } + guard let password = self?.passwordText else { + output.errMessage.accept("비밀번호가 비어있습니다.") + return + } + + let idRegEx = "[A-Za-z0-9]{5,13}" + let pwRegEx = "[A-Za-z0-9!_@$%^&+=]{8,20}" + + guard let _ = id.range(of: idRegEx, options: .regularExpression) else { + output.errMessage.accept("아이디가 유효하지 않습니다.") + return + } + + guard let _ = password.range(of: pwRegEx, options: .regularExpression) else { + output.errMessage.accept("비밀번호가 유효하지 않습니다.") + return + } + output.isValid.accept(Void()) + }).disposed(by: disposeBag) + + return output + } +} diff --git a/34th-SOPT-iOS-DanggeunCloneCoding/7th-Seminar/DI + Rx + input-output Pattern/ViewModelType(input-output Pattern).swift b/34th-SOPT-iOS-DanggeunCloneCoding/7th-Seminar/DI + Rx + input-output Pattern/ViewModelType(input-output Pattern).swift new file mode 100644 index 0000000..bf07b5f --- /dev/null +++ b/34th-SOPT-iOS-DanggeunCloneCoding/7th-Seminar/DI + Rx + input-output Pattern/ViewModelType(input-output Pattern).swift @@ -0,0 +1,15 @@ +// +// ViewModelType.swift +// 34th-SOPT-iOS-DanggeunCloneCoding +// +// Created by 김민성 on 2024/05/25. +// + +import RxSwift + +protocol ViewModelType { + associatedtype Input + associatedtype Output + + func transform(from input: Input, disposeBag: RxSwift.DisposeBag) -> Output +} diff --git a/34th-SOPT-iOS-DanggeunCloneCoding/7th-Seminar/DI/LoginViewController_DI.swift b/34th-SOPT-iOS-DanggeunCloneCoding/7th-Seminar/DI/LoginViewController_DI.swift new file mode 100644 index 0000000..ee9b49d --- /dev/null +++ b/34th-SOPT-iOS-DanggeunCloneCoding/7th-Seminar/DI/LoginViewController_DI.swift @@ -0,0 +1,80 @@ +// +// LoginViewController_DI.swift +// 34th-SOPT-iOS-DanggeunCloneCoding +// +// Created by 김민성 on 2024/05/25. +// + +import UIKit + +import SnapKit + +final class LoginViewController_DI: UIViewController { + + private let rootView = LoginView() + private let viewModel: any LoginViewModelType + + override func loadView() { + self.view = rootView + } + + init(viewModel: any LoginViewModelType) { + self.viewModel = viewModel + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + + self.view.backgroundColor = .white + + setTarget() + bindViewModel() + } + + private func setTarget() { + rootView.loginButton.addTarget(self, action: #selector(loginButtonDidTap), for: .touchUpInside) + } + + private func bindViewModel() { + viewModel.isValid.bind { [weak self] isValid in + guard let isValid else { return } + if isValid { self?.pushToWelcomeVC() } + } + + viewModel.errMessage.bind { [weak self] err in + guard let err else { return } + self?.showToast(err) + } + } + + + @objc private func loginButtonDidTap() { + viewModel.checkValid( + id: rootView.idTextField.text, + password: rootView.passwordTextField.text + ) + } + + private func pushToWelcomeVC() { + let welcomeViewController = WelcomeViewController() + self.navigationController?.pushViewController(welcomeViewController, animated: true) + } + + func showToast(_ message: String, + bottomInset: CGFloat = 86 + ) { + guard let view else { return } + Toast().show(message: message, + view: view, + bottomInset: bottomInset + ) + } +} + + + diff --git a/34th-SOPT-iOS-DanggeunCloneCoding/7th-Seminar/DI/LoginViewModelType.swift b/34th-SOPT-iOS-DanggeunCloneCoding/7th-Seminar/DI/LoginViewModelType.swift new file mode 100644 index 0000000..9aaab17 --- /dev/null +++ b/34th-SOPT-iOS-DanggeunCloneCoding/7th-Seminar/DI/LoginViewModelType.swift @@ -0,0 +1,13 @@ +// +// LoginViewModelType.swift +// 34th-SOPT-iOS-DanggeunCloneCoding +// +// Created by 김민성 on 2024/05/31. +// + +protocol LoginViewModelType { + var isValid: ObservablePattern { get } + var errMessage: ObservablePattern { get } + + func checkValid(id: String?, password: String?) +} diff --git a/34th-SOPT-iOS-DanggeunCloneCoding/7th-Seminar/DI/LoginViewModel_DI.swift b/34th-SOPT-iOS-DanggeunCloneCoding/7th-Seminar/DI/LoginViewModel_DI.swift new file mode 100644 index 0000000..cbbe2c7 --- /dev/null +++ b/34th-SOPT-iOS-DanggeunCloneCoding/7th-Seminar/DI/LoginViewModel_DI.swift @@ -0,0 +1,41 @@ +// +// LoginViewModel_DI.swift +// 34th-SOPT-iOS-DanggeunCloneCoding +// +// Created by 김민성 on 2024/05/31. +// + +import UIKit + +import SnapKit + +final class LoginViewModel_DI: LoginViewModelType { + var isValid: ObservablePattern = ObservablePattern.init(false) + var errMessage: ObservablePattern = ObservablePattern.init(nil) + + func checkValid(id: String?, password: String?) { + guard let id else { + errMessage.value = "아이디가 비어있습니다." + return + } + guard let password else { + errMessage.value = "비밀번호가 비어있습니다." + return + } + + let idRegEx = "[A-Za-z0-9]{5,13}" + let pwRegEx = "[A-Za-z0-9!_@$%^&+=]{8,20}" + + guard let _ = id.range(of: idRegEx, options: .regularExpression) else { + errMessage.value = "아이디가 유효하지 않습니다." + return + } + + guard let _ = password.range(of: pwRegEx, options: .regularExpression) else { + errMessage.value = "비밀번호가 유효하지 않습니다." + return + } + isValid.value = true + + } +} diff --git a/34th-SOPT-iOS-DanggeunCloneCoding/7th-Seminar/LoginViewController_LiveCodingTemplate.swift b/34th-SOPT-iOS-DanggeunCloneCoding/7th-Seminar/LoginViewController_LiveCodingTemplate.swift new file mode 100644 index 0000000..7c0d285 --- /dev/null +++ b/34th-SOPT-iOS-DanggeunCloneCoding/7th-Seminar/LoginViewController_LiveCodingTemplate.swift @@ -0,0 +1,49 @@ +// +// LoginViewController_LiveCodingTemplate.swift +// 34th-SOPT-iOS-DanggeunCloneCoding +// +// Created by 김민성 on 2024/05/25. +// + +import UIKit +import SnapKit + +import RxSwift + +final class LoginViewController_LiveCoding: UIViewController { + + private let rootView = LoginView() + + override func loadView() { + self.view = rootView + } + + override func viewDidLoad() { + super.viewDidLoad() + + self.view.backgroundColor = .white + + bindViewModel() + } + + + private func bindViewModel() { + } + + private func pushToWelcomeVC() { + let welcomeViewController = WelcomeViewController() + self.navigationController?.pushViewController(welcomeViewController, animated: true) + } + + func showToast(_ message: String, + bottomInset: CGFloat = 86 + ) { + guard let view else { return } + Toast().show(message: message, + view: view, + bottomInset: bottomInset + ) + } +} + + diff --git a/34th-SOPT-iOS-DanggeunCloneCoding/CheckUserInfoViewController.swift b/34th-SOPT-iOS-DanggeunCloneCoding/CheckUserInfoViewController.swift new file mode 100644 index 0000000..0bf4e9a --- /dev/null +++ b/34th-SOPT-iOS-DanggeunCloneCoding/CheckUserInfoViewController.swift @@ -0,0 +1,92 @@ +// +// CheckUserInfoViewController.swift +// 34th-SOPT-iOS-DanggeunCloneCoding +// +// Created by 김민성 on 2024/04/27. +// + +import Foundation +import UIKit + +final class CheckUserInfoViewController: UIViewController { + private let logoImageView = UIImageView(image: .dangGeuni) + private let idLabel = UILabel() + private let nickNameLabel = UILabel() + private let phoneNumberLabel = UILabel() + + override func viewDidLoad() { + super.viewDidLoad() + + setStyle() + setLayout() + requestUserInfo() + } + + private func requestUserInfo() { + //memeberId에 아까 받은 멤버 아이디 직접 넣어주기 + UserService.shared.getUserInfo(memberId: "\(11)") { [weak self] response in + switch response { + case .success(let data): + guard let data = data as? UserInfoResponseModel else { + return } + self?.idLabel.text = data.data.authenticationId + self?.nickNameLabel.text = data.data.nickname + self?.phoneNumberLabel.text = data.data.phone + case .requestErr: + print("요청 오류 입니다") + case .decodedErr: + print("디코딩 오류 입니다") + case .pathErr: + print("경로 오류 입니다") + case .serverErr: + print("서버 오류입니다") + case .networkFail: + print("네트워크 오류입니다") + } + } + } + + private func setStyle() { + self.view.backgroundColor = .white + + idLabel.do { + $0.font = UIFont(name: "Pretendard-ExtraBold", size: 25) + $0.textAlignment = .center + } + + nickNameLabel.do { + $0.font = UIFont(name: "Pretendard-ExtraBold", size: 25) + $0.textAlignment = .center + } + + phoneNumberLabel.do { + $0.font = UIFont(name: "Pretendard-ExtraBold", size: 25) + $0.textAlignment = .center + } + } + + private func setLayout() { + [logoImageView, idLabel, nickNameLabel, phoneNumberLabel].forEach { + self.view.addSubview($0) + } + + logoImageView.snp.makeConstraints { + $0.top.equalToSuperview().offset(87) + $0.centerX.equalToSuperview() + $0.size.equalTo(150) + } + + idLabel.snp.makeConstraints { + $0.top.equalTo(logoImageView.snp.bottom).offset(58) + $0.centerX.equalToSuperview() + } + nickNameLabel.snp.makeConstraints { + $0.top.equalTo(idLabel.snp.bottom).offset(23) + $0.centerX.equalToSuperview() + } + phoneNumberLabel.snp.makeConstraints { + $0.top.equalTo(nickNameLabel.snp.bottom).offset(23) + $0.centerX.equalToSuperview() + } + } +} diff --git a/34th-SOPT-iOS-DanggeunCloneCoding/Config.swift b/34th-SOPT-iOS-DanggeunCloneCoding/Config.swift new file mode 100644 index 0000000..0f96e95 --- /dev/null +++ b/34th-SOPT-iOS-DanggeunCloneCoding/Config.swift @@ -0,0 +1,35 @@ +// +// Config.swift +// 34th-SOPT-iOS-DanggeunCloneCoding +// +// Created by 김민성 on 2024/04/27. +// + +import Foundation + +enum Config { + + enum Keys { + enum Plist { + static let baseURL = "BASE_URL" + } + } + + private static let infoDictionary: [String: Any] = { + guard let dict = Bundle.main.infoDictionary else { + fatalError("plist cannot found") + } + return dict + }() +} + + +extension Config { + + static let baseURL: String = { + guard let key = Config.infoDictionary[Keys.Plist.baseURL] as? String else { + fatalError("Base URL is not set in plist for this configuration.") + } + return key + }() +} diff --git a/34th-SOPT-iOS-DanggeunCloneCoding/AppDelegate.swift b/34th-SOPT-iOS-DanggeunCloneCoding/Global/Application/AppDelegate.swift similarity index 100% rename from 34th-SOPT-iOS-DanggeunCloneCoding/AppDelegate.swift rename to 34th-SOPT-iOS-DanggeunCloneCoding/Global/Application/AppDelegate.swift diff --git a/34th-SOPT-iOS-DanggeunCloneCoding/SceneDelegate.swift b/34th-SOPT-iOS-DanggeunCloneCoding/Global/Application/SceneDelegate.swift similarity index 100% rename from 34th-SOPT-iOS-DanggeunCloneCoding/SceneDelegate.swift rename to 34th-SOPT-iOS-DanggeunCloneCoding/Global/Application/SceneDelegate.swift diff --git a/34th-SOPT-iOS-DanggeunCloneCoding/UIFontExtension.swift b/34th-SOPT-iOS-DanggeunCloneCoding/Global/Extensions/UIFontExtension.swift similarity index 100% rename from 34th-SOPT-iOS-DanggeunCloneCoding/UIFontExtension.swift rename to 34th-SOPT-iOS-DanggeunCloneCoding/Global/Extensions/UIFontExtension.swift diff --git a/34th-SOPT-iOS-DanggeunCloneCoding/UITextFieldExtensions.swift b/34th-SOPT-iOS-DanggeunCloneCoding/Global/Extensions/UITextFieldExtensions.swift similarity index 100% rename from 34th-SOPT-iOS-DanggeunCloneCoding/UITextFieldExtensions.swift rename to 34th-SOPT-iOS-DanggeunCloneCoding/Global/Extensions/UITextFieldExtensions.swift diff --git a/34th-SOPT-iOS-DanggeunCloneCoding/Global/ObservablePattern.swift b/34th-SOPT-iOS-DanggeunCloneCoding/Global/ObservablePattern.swift new file mode 100644 index 0000000..abe6947 --- /dev/null +++ b/34th-SOPT-iOS-DanggeunCloneCoding/Global/ObservablePattern.swift @@ -0,0 +1,27 @@ +// +// ObservablePattern.swift +// 34th-SOPT-iOS-DanggeunCloneCoding +// +// Created by 김민성 on 2024/05/31. +// + +class ObservablePattern { // --- a + + var value: T? { // --- b + didSet { + self.listener?(value) + } + } + + init(_ value: T?) { + self.value = value + } + + private var listener: ((T?) -> Void)? // --- c + + func bind(_ listener: @escaping (T?) -> Void) { + listener(value) // 생략 가능, 여기선 시작되는 순간부터 초기값을 갖고 동작하기 위해 사용 + self.listener = listener + } +} + diff --git a/34th-SOPT-iOS-DanggeunCloneCoding/Global/Toast.swift b/34th-SOPT-iOS-DanggeunCloneCoding/Global/Toast.swift new file mode 100644 index 0000000..a4099d7 --- /dev/null +++ b/34th-SOPT-iOS-DanggeunCloneCoding/Global/Toast.swift @@ -0,0 +1,106 @@ +// +// Toast.swift +// 34th-SOPT-iOS-DanggeunCloneCoding +// +// Created by 김민성 on 2024/05/31. +// + +import UIKit + +import SnapKit + + +final class Toast: UIView { + + func show( + message: String, + view: UIView, + bottomInset: CGFloat) + { + let toastLabel = UILabel() + + self.backgroundColor = .black + self.alpha = 1 + self.isUserInteractionEnabled = false + + + toastLabel.textColor = .white + toastLabel.textAlignment = .center + toastLabel.text = message + toastLabel.clipsToBounds = true + toastLabel.numberOfLines = 0 + toastLabel.sizeToFit() + + + view.addSubview(self) + self.addSubview(toastLabel) + + + self.snp.makeConstraints { + $0.centerX.equalToSuperview() + $0.bottom.equalToSuperview().inset(100) + } + + toastLabel.snp.makeConstraints { + $0.horizontalEdges.equalToSuperview().inset(24) + $0.verticalEdges.equalToSuperview().inset(12) + } + + UIView.animate(withDuration: 0.4, delay: 0.0, options: .curveEaseIn, animations: { + self.alpha = 1.0 + }, completion: { _ in + UIView.animate(withDuration: 1, delay: 1.8, options: .curveEaseOut, animations: { + self.alpha = 0.0 + }, completion: {_ in + self.removeFromSuperview() + }) + }) + } + + func show(message: String, + view: UIView, + topInset: CGFloat) { + let toastLabel = UILabel() + + self.backgroundColor = .black + self.alpha = 1 + self.isUserInteractionEnabled = false + + + toastLabel.textColor = .white + toastLabel.textAlignment = .center + toastLabel.text = message + toastLabel.clipsToBounds = true + toastLabel.numberOfLines = 0 + toastLabel.sizeToFit() + + + view.addSubview(self) + self.addSubview(toastLabel) + + + self.snp.makeConstraints { + $0.centerX.equalToSuperview() + $0.top.equalToSuperview().inset(100) + } + + toastLabel.snp.makeConstraints { + $0.horizontalEdges.equalToSuperview().inset(24) + $0.verticalEdges.equalToSuperview().inset(12) + } + + + UIView.animate(withDuration: 0.6, delay: 0.0, options: .curveEaseIn, animations: { + self.alpha = 1.0 + }, completion: { _ in + UIView.animate(withDuration: 1, delay: 1.8, options: .curveEaseOut, animations: { + self.alpha = 0.0 + }, completion: {_ in + self.removeFromSuperview() + }) + }) + } + +} + + diff --git a/34th-SOPT-iOS-DanggeunCloneCoding/Info.plist b/34th-SOPT-iOS-DanggeunCloneCoding/Info.plist index 2d8eb0a..dcd3903 100644 --- a/34th-SOPT-iOS-DanggeunCloneCoding/Info.plist +++ b/34th-SOPT-iOS-DanggeunCloneCoding/Info.plist @@ -2,6 +2,13 @@ + BASE_URL + $(BASE_URL) + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + UIAppFonts Pretendard-Thin.otf diff --git a/34th-SOPT-iOS-DanggeunCloneCoding/LoginView/ViewControllers/LoginViewController.swift b/34th-SOPT-iOS-DanggeunCloneCoding/LoginView/ViewControllers/LoginViewController.swift new file mode 100644 index 0000000..56cd6ba --- /dev/null +++ b/34th-SOPT-iOS-DanggeunCloneCoding/LoginView/ViewControllers/LoginViewController.swift @@ -0,0 +1,227 @@ +// +// LoginViewController.swift +// 34th-SOPT-iOS-DanggeunCloneCoding +// +// Created by 김민성 on 2024/04/19. +// + +import UIKit +import SnapKit + + +class LoginViewController: UIViewController { + +// private lazy var customInputAccessoryView: UIView = { +// let bar = UIToolbar(frame: CGRect(origin: .zero, size: CGSize(width: 100, height: 100))) +// let hideKeyboardButton = UIBarButtonItem( +// image: UIImage(systemName: "keyboard.chevron.compact.down"), +// style: .plain, +// target: self, +// action: #selector(keyboardHideButtonTapped) +// ) +// hideKeyboardButton.tintColor = UIColor.dangGeunOrange +// let flexibleBarButton = UIBarButtonItem(systemItem: UIBarButtonItem.SystemItem.flexibleSpace) +// bar.items = [flexibleBarButton, hideKeyboardButton] +// bar.sizeToFit() +// return bar +// }() +// +// private let titleLabel: UILabel = { +// let label = UILabel() +// label.text = "동네라서 가능한 모든것\n당근에서 가까운 이웃과 함께해요." +// label.textColor = .black +// label.textAlignment = .center +// label.numberOfLines = 2 +// label.font = UIFont.pretendardFont(ofSize: 18, weight: 700) //bold +// label.clipsToBounds = true +// return label +// }() +// +// private lazy var idTextField: UITextField = { +// let tf = UITextField() +// tf.addLeftPadding() +// tf.placeholder = "아이디를 입력해주세요" +// tf.font = UIFont.pretendardFont(ofSize: 18, weight: 600) //SemiBold +// tf.backgroundColor = UIColor(red: 221/255, green: 222/255, blue: 227/255, alpha: 1) +// tf.borderStyle = .none +// tf.layer.cornerRadius = 8 +// tf.layer.cornerCurve = .continuous +// tf.inputAccessoryView = self.customInputAccessoryView +// return tf +// }() +// +// private lazy var passwordTextField: UITextField = { +// let tf = UITextField() +// tf.addLeftPadding() +// tf.placeholder = "비밀번호를 입력해주세요" +// tf.font = UIFont.systemFont(ofSize: 14, weight: UIFont.Weight.semibold) //SemiBold +// tf.backgroundColor = UIColor(red: 221/255, green: 222/255, blue: 227/255, alpha: 1) +// tf.borderStyle = .none +// tf.layer.cornerRadius = 8 +// tf.layer.cornerCurve = .continuous +// tf.inputAccessoryView = self.customInputAccessoryView +// +// return tf +// }() +// +// private lazy var loginButton: UIButton = { +// let button = UIButton() +// button.backgroundColor = UIColor.dangGeunOrange +// button.setTitle("로그인하기", for: UIControl.State.normal) +// button.titleLabel?.font = UIFont.pretendardFont(ofSize: 18, weight: 700) //bold +// button.addTarget(self, action: #selector(loginButtonDidTap), for: UIControl.Event.touchUpInside) +// button.clipsToBounds = true +// button.layer.cornerRadius = 13 +// button.layer.cornerCurve = .continuous +// return button +// }() +// +// private lazy var signUpButton: UIButton = { +// let button = UIButton() +// button.backgroundColor = UIColor.dangGeunOrange +// button.setTitle("회원가입하기", for: UIControl.State.normal) +// button.titleLabel?.font = UIFont.pretendardFont(ofSize: 18, weight: 700) //bold +// button.addTarget(self, action: #selector(signUpButtonDidTap), for: UIControl.Event.touchUpInside) +// button.clipsToBounds = true +// button.layer.cornerRadius = 13 +// button.layer.cornerCurve = .continuous +// return button +// }() + + let rootView = LoginView() + + override func loadView() { + self.view = rootView + } + + override func viewDidLoad() { + super.viewDidLoad() + self.setupUI() + self.setButtonsAction() +// self.configureHierarchy() +// self.setLayout() + + + } + + + private func setupUI() { + self.view.backgroundColor = .systemBackground + } + + private func setButtonsAction() { + self.rootView.loginButton.addTarget(self, action: #selector(loginButtonDidTap), for: .touchUpInside) + self.rootView.signUpButton.addTarget(self, action: #selector(signUpButtonDidTap), for: .touchUpInside) + } + +// private func configureHierarchy() { +// [titleLabel, idTextField, passwordTextField, loginButton, signUpButton].forEach { self.view.addSubview($0) } +// } + + +// private func setLayout() { +// [self.titleLabel, self.idTextField, self.passwordTextField, self.loginButton].forEach { view in +// view.translatesAutoresizingMaskIntoConstraints = false +// } +// +// self.titleLabel.snp.makeConstraints { label in +// label.centerX.equalToSuperview() +// label.top.equalToSuperview().offset(161) +// } +// +// self.idTextField.snp.makeConstraints { tf in +// tf.centerX.equalToSuperview() +// tf.top.equalTo(self.titleLabel.snp.bottom).offset(71) +// tf.width.equalTo(335) +// tf.height.equalTo(53) +// } +// +// self.passwordTextField.snp.makeConstraints { tf in +// tf.centerX.equalToSuperview() +// tf.top.equalTo(self.idTextField.snp.bottom).offset(7) +// tf.width.equalTo(335) +// tf.height.equalTo(53) +// } +// +// self.loginButton.snp.makeConstraints { btn in +// btn.centerX.equalToSuperview() +// btn.top.equalTo(self.passwordTextField.snp.bottom).offset(75) +// btn.width.equalTo(335) +// btn.height.equalTo(58) +// } +// +// self.signUpButton.snp.makeConstraints { btn in +// btn.centerX.equalToSuperview() +// btn.top.equalTo(self.loginButton.snp.bottom).offset(30) +// btn.width.equalTo(335) +// btn.height.equalTo(58) +// } +// /* +// NSLayoutConstraint.activate( +// +// [self.titleLabel.centerXAnchor.constraint(equalTo: self.view.centerXAnchor), +// self.titleLabel.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 161), +// self.titleLabel.widthAnchor.constraint(equalToConstant: 236), +// self.titleLabel.heightAnchor.constraint(equalToConstant: 44), +// +// self.idTextField.centerXAnchor.constraint(equalTo: self.view.centerXAnchor), +// self.idTextField.topAnchor.constraint(equalTo: self.titleLabel.bottomAnchor, constant: 71), +// self.idTextField.widthAnchor.constraint(equalToConstant: 335), +// self.idTextField.heightAnchor.constraint(equalToConstant: 53), +// +// self.passwordTextField.centerXAnchor.constraint(equalTo: self.view.centerXAnchor), +// self.passwordTextField.topAnchor.constraint(equalTo: self.idTextField.bottomAnchor, constant: 7), +// self.passwordTextField.widthAnchor.constraint(equalToConstant: 335), +// self.passwordTextField.heightAnchor.constraint(equalToConstant: 53), +// +// +// self.loginButton.centerXAnchor.constraint(equalTo: self.view.centerXAnchor), +// self.loginButton.topAnchor.constraint(equalTo: self.passwordTextField.bottomAnchor, constant: 75), +// self.loginButton.widthAnchor.constraint(equalToConstant: 335), +// self.loginButton.heightAnchor.constraint(equalToConstant: 58) +// ] +// ) +// */ +// } + + @objc private func loginButtonDidTap() { + self.view.endEditing(true) + + /* + 서버에 회원정보 있는지 요청하는 기능을 구현할 경우, 여기에서 코드 작성 후, 예외처리 + */ + + //self.presentToWelcomeVC() + self.pushToWelcomeVC() + } + + @objc private func signUpButtonDidTap() { + self.view.endEditing(true) + self.pushToSignUpVC() + } + +// @objc private func keyboardHideButtonTapped() { +// self.view.endEditing(true) +// } + + + private func presentToWelcomeVC() { + let welcomeViewController = WelcomeViewController() + welcomeViewController.modalPresentationStyle = .formSheet + welcomeViewController.id = self.rootView.idTextField.text + self.present(welcomeViewController, animated: true) + } + + + private func pushToWelcomeVC() { + let welcomeViewController = WelcomeViewController() + welcomeViewController.id = self.rootView.idTextField.text + self.navigationController?.pushViewController(welcomeViewController, animated: true) + } + + private func pushToSignUpVC() { + let signUpVC = SignUpViewController() + self.navigationController?.pushViewController(signUpVC, animated: true) + } + +} diff --git a/34th-SOPT-iOS-DanggeunCloneCoding/LoginView/Views/.gitkeep b/34th-SOPT-iOS-DanggeunCloneCoding/LoginView/Views/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/34th-SOPT-iOS-DanggeunCloneCoding/LoginViewController.swift b/34th-SOPT-iOS-DanggeunCloneCoding/LoginView/Views/LoginView.swift similarity index 77% rename from 34th-SOPT-iOS-DanggeunCloneCoding/LoginViewController.swift rename to 34th-SOPT-iOS-DanggeunCloneCoding/LoginView/Views/LoginView.swift index 5217379..92d7417 100644 --- a/34th-SOPT-iOS-DanggeunCloneCoding/LoginViewController.swift +++ b/34th-SOPT-iOS-DanggeunCloneCoding/LoginView/Views/LoginView.swift @@ -1,16 +1,14 @@ // -// LoginViewController.swift +// LoginView.swift // 34th-SOPT-iOS-DanggeunCloneCoding // -// Created by 김민성 on 2024/04/19. +// Created by 김민성 on 2024/05/31. // import UIKit -import SnapKit - - -class LoginViewController: UIViewController { +final class LoginView: UIView { + private lazy var customInputAccessoryView: UIView = { let bar = UIToolbar(frame: CGRect(origin: .zero, size: CGSize(width: 100, height: 100))) let hideKeyboardButton = UIBarButtonItem( @@ -37,7 +35,7 @@ class LoginViewController: UIViewController { return label }() - private lazy var idTextField: UITextField = { + lazy var idTextField: UITextField = { let tf = UITextField() tf.addLeftPadding() tf.placeholder = "아이디를 입력해주세요" @@ -50,7 +48,7 @@ class LoginViewController: UIViewController { return tf }() - private lazy var passwordTextField: UITextField = { + lazy var passwordTextField: UITextField = { let tf = UITextField() tf.addLeftPadding() tf.placeholder = "비밀번호를 입력해주세요" @@ -64,37 +62,47 @@ class LoginViewController: UIViewController { return tf }() - private lazy var loginButton: UIButton = { + lazy var loginButton: UIButton = { let button = UIButton() button.backgroundColor = UIColor.dangGeunOrange button.setTitle("로그인하기", for: UIControl.State.normal) button.titleLabel?.font = UIFont.pretendardFont(ofSize: 18, weight: 700) //bold - button.addTarget(self, action: #selector(loginButtonDidTap), for: UIControl.Event.touchUpInside) + //button.addTarget(self, action: #selector(loginButtonDidTap), for: UIControl.Event.touchUpInside) button.clipsToBounds = true button.layer.cornerRadius = 13 button.layer.cornerCurve = .continuous return button }() - - - override func viewDidLoad() { - super.viewDidLoad() - self.setupUI() - self.configureHierarchy() - self.setLayout() - } - - - private func setupUI() { - self.view.backgroundColor = .systemBackground + + lazy var signUpButton: UIButton = { + let button = UIButton() + button.backgroundColor = UIColor.dangGeunOrange + button.setTitle("회원가입하기", for: UIControl.State.normal) + button.titleLabel?.font = UIFont.pretendardFont(ofSize: 18, weight: 700) //bold + //button.addTarget(self, action: #selector(signUpButtonDidTap), for: UIControl.Event.touchUpInside) + button.clipsToBounds = true + button.layer.cornerRadius = 13 + button.layer.cornerCurve = .continuous + return button + }() + + + override init(frame: CGRect) { + super.init(frame: frame) + + self.configureViewHierarchy() + self.setConstraints() } - - private func configureHierarchy() { - [titleLabel, idTextField, passwordTextField, loginButton].forEach { self.view.addSubview($0) } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") } + private func configureViewHierarchy() { + [titleLabel, idTextField, passwordTextField, loginButton, signUpButton].forEach { self.addSubview($0) } + } - private func setLayout() { + private func setConstraints() { [self.titleLabel, self.idTextField, self.passwordTextField, self.loginButton].forEach { view in view.translatesAutoresizingMaskIntoConstraints = false } @@ -125,6 +133,12 @@ class LoginViewController: UIViewController { btn.height.equalTo(58) } + self.signUpButton.snp.makeConstraints { btn in + btn.centerX.equalToSuperview() + btn.top.equalTo(self.loginButton.snp.bottom).offset(30) + btn.width.equalTo(335) + btn.height.equalTo(58) + } /* NSLayoutConstraint.activate( @@ -153,31 +167,8 @@ class LoginViewController: UIViewController { */ } - - - @objc private func loginButtonDidTap() { - self.view.endEditing(true) - //self.presentToWelcomeVC() - self.pushToWelcomeVC() - } - @objc private func keyboardHideButtonTapped() { - self.view.endEditing(true) + self.endEditing(true) } - - - private func presentToWelcomeVC() { - let welcomeViewController = WelcomeViewController() - welcomeViewController.modalPresentationStyle = .formSheet - welcomeViewController.id = self.idTextField.text - self.present(welcomeViewController, animated: true) - } - - - private func pushToWelcomeVC() { - let welcomeViewController = WelcomeViewController() - welcomeViewController.id = self.idTextField.text - self.navigationController?.pushViewController(welcomeViewController, animated: true) - } - + } diff --git a/34th-SOPT-iOS-DanggeunCloneCoding/MoyaLoggingPlugin.swift b/34th-SOPT-iOS-DanggeunCloneCoding/MoyaLoggingPlugin.swift new file mode 100644 index 0000000..014bebc --- /dev/null +++ b/34th-SOPT-iOS-DanggeunCloneCoding/MoyaLoggingPlugin.swift @@ -0,0 +1,72 @@ +// +// MoyaLoggingPlugin.swift +// 34th-SOPT-iOS-DanggeunCloneCoding +// +// Created by 김민성 on 2024/04/27. +// + +import UIKit + +import Moya + +final class MoyaLoggingPlugin: PluginType { + + // MARK: - Request 보낼 시 호출 + func willSend(_ request: RequestType, target: TargetType) { + guard let httpRequest = request.request else { + print("--> 유효하지 않은 요청") + return + } + let url = httpRequest.description + let method = httpRequest.httpMethod ?? "메소드값이 nil입니다." + var log = "----------------------------------------------------\n1️⃣[\(method)] \(url)\n----------------------------------------------------\n" + log.append("2️⃣API: \(target)\n") + if let headers = httpRequest.allHTTPHeaderFields, !headers.isEmpty { + log.append("header: \(headers)\n") + } + if let body = httpRequest.httpBody, let bodyString = String(bytes: body, encoding: String.Encoding.utf8) { + log.append("\(bodyString)\n") + } + log.append("------------------- END \(method) -------------------") + print(log) + } + + // MARK: - Response 받을 시 호출 + func didReceive(_ result: Result, target: TargetType) { + switch result { + case let .success(response): + self.onSucceed(response, target: target) + case let .failure(error): + self.onFail(error, target: target) + } + } + + func onSucceed(_ response: Response, target: TargetType) { + let request = response.request + let url = request?.url?.absoluteString ?? "nil" + let statusCode = response.statusCode + var log = "------------------- Reponse가 도착했습니다. -------------------" + log.append("\n3️⃣[\(statusCode)] \(url)\n") + log.append("API: \(target)\n") + log.append("Status Code: [\(statusCode)]\n") + log.append("URL: \(url)\n") + log.append("response: \n") + if let reString = String(bytes: response.data, encoding: String.Encoding.utf8) { + log.append("4️⃣\(reString)\n") + } + log.append("------------------- END HTTP -------------------") + print(log) + } + + func onFail(_ error: MoyaError, target: TargetType) { + if let response = error.response { + onSucceed(response, target: target) + return + } + var log = "네트워크 오류" + log.append("<-- \(error.errorCode)\n") + log.append("\(error.failureReason ?? error.errorDescription ?? "unknown error")\n") + log.append("<-- END HTTP") + print(log) + } +} diff --git a/34th-SOPT-iOS-DanggeunCloneCoding/NetworkResult.swift b/34th-SOPT-iOS-DanggeunCloneCoding/NetworkResult.swift new file mode 100644 index 0000000..3670c47 --- /dev/null +++ b/34th-SOPT-iOS-DanggeunCloneCoding/NetworkResult.swift @@ -0,0 +1,17 @@ +// +// NetworkResult.swift +// 34th-SOPT-iOS-DanggeunCloneCoding +// +// Created by 김민성 on 2024/04/27. +// + +import Foundation + +enum NetworkResult { + case success(T) + case requestErr // 요청 에러 발생했을 때, + case decodedErr // 디코딩 오류 발생했을 때 + case pathErr // 경로 에러 발생했을 때, + case serverErr // 서버의 내부적 에러가 발생했을 때, + case networkFail // 네트워크 연결 실패했을 때 +} diff --git a/34th-SOPT-iOS-DanggeunCloneCoding/Networking/Model/SignUpRequestModel.swift b/34th-SOPT-iOS-DanggeunCloneCoding/Networking/Model/SignUpRequestModel.swift new file mode 100644 index 0000000..9e35f66 --- /dev/null +++ b/34th-SOPT-iOS-DanggeunCloneCoding/Networking/Model/SignUpRequestModel.swift @@ -0,0 +1,15 @@ +// +// SignUpRequestModel.swift +// 34th-SOPT-iOS-DanggeunCloneCoding +// +// Created by 김민성 on 2024/04/27. +// + +import Foundation + +struct SignUpRequestModel: Codable { + let authenticationId: String + let password: String + let nickname: String + let phone: String +} diff --git a/34th-SOPT-iOS-DanggeunCloneCoding/Networking/Model/SignUpResponseModel 2.swift b/34th-SOPT-iOS-DanggeunCloneCoding/Networking/Model/SignUpResponseModel 2.swift new file mode 100644 index 0000000..4e52a9d --- /dev/null +++ b/34th-SOPT-iOS-DanggeunCloneCoding/Networking/Model/SignUpResponseModel 2.swift @@ -0,0 +1,13 @@ +// +// SignUpResponseModel.swift +// 34th-SOPT-iOS-DanggeunCloneCoding +// +// Created by 김민성 on 2024/04/27. +// + +import Foundation + +struct SignUpResponseModel: Codable { + let code: Int + let message: String +} diff --git a/34th-SOPT-iOS-DanggeunCloneCoding/Networking/Model/SignUpResponseModel.swift b/34th-SOPT-iOS-DanggeunCloneCoding/Networking/Model/SignUpResponseModel.swift new file mode 100644 index 0000000..4e52a9d --- /dev/null +++ b/34th-SOPT-iOS-DanggeunCloneCoding/Networking/Model/SignUpResponseModel.swift @@ -0,0 +1,13 @@ +// +// SignUpResponseModel.swift +// 34th-SOPT-iOS-DanggeunCloneCoding +// +// Created by 김민성 on 2024/04/27. +// + +import Foundation + +struct SignUpResponseModel: Codable { + let code: Int + let message: String +} diff --git a/34th-SOPT-iOS-DanggeunCloneCoding/Networking/Model/UserInfoResponseModel 2.swift b/34th-SOPT-iOS-DanggeunCloneCoding/Networking/Model/UserInfoResponseModel 2.swift new file mode 100644 index 0000000..d53a603 --- /dev/null +++ b/34th-SOPT-iOS-DanggeunCloneCoding/Networking/Model/UserInfoResponseModel 2.swift @@ -0,0 +1,20 @@ +// +// UserInfoResponseModel.swift +// 34th-SOPT-iOS-DanggeunCloneCoding +// +// Created by 김민성 on 2024/04/27. +// + +import Foundation + +// MARK: - UserInfoResponseModel +struct UserInfoResponseModel: Codable { + let code: Int + let message: String + let data: DataClass +} + +// MARK: - DataClass +struct DataClass: Codable { + let authenticationId, nickname, phone: String +} diff --git a/34th-SOPT-iOS-DanggeunCloneCoding/Networking/Model/UserInfoResponseModel.swift b/34th-SOPT-iOS-DanggeunCloneCoding/Networking/Model/UserInfoResponseModel.swift new file mode 100644 index 0000000..d53a603 --- /dev/null +++ b/34th-SOPT-iOS-DanggeunCloneCoding/Networking/Model/UserInfoResponseModel.swift @@ -0,0 +1,20 @@ +// +// UserInfoResponseModel.swift +// 34th-SOPT-iOS-DanggeunCloneCoding +// +// Created by 김민성 on 2024/04/27. +// + +import Foundation + +// MARK: - UserInfoResponseModel +struct UserInfoResponseModel: Codable { + let code: Int + let message: String + let data: DataClass +} + +// MARK: - DataClass +struct DataClass: Codable { + let authenticationId, nickname, phone: String +} diff --git a/34th-SOPT-iOS-DanggeunCloneCoding/SignUpViewController.swift b/34th-SOPT-iOS-DanggeunCloneCoding/SignUpViewController.swift new file mode 100644 index 0000000..155874c --- /dev/null +++ b/34th-SOPT-iOS-DanggeunCloneCoding/SignUpViewController.swift @@ -0,0 +1,187 @@ +// +// SignUpViewController.swift +// 34th-SOPT-iOS-DanggeunCloneCoding +// +// Created by 김민성 on 2024/04/27. +// + +import UIKit + +import SnapKit +import Then +import Moya + +final class SignUpView: UIView { + + private let titleLabel = UILabel() + let idTextField = UITextField() + let passwordTextField = UITextField() + let nickNameTextField = UITextField() + let phoneNumberTextField = UITextField() + lazy var signUpButton = UIButton() + + override init(frame: CGRect) { + super.init(frame: frame) + + setStyle() + setLayout() + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func setStyle() { + titleLabel.do { + $0.text = "회원가입" + $0.textColor = .black + $0.textAlignment = .center + $0.numberOfLines = 2 + $0.font = UIFont(name: "Pretendard-Bold", size: 40) + } + + idTextField.do { + $0.placeholder = "아이디를 입력해주세요" + $0.font = UIFont(name: "Pretendard-SemiBold", size: 14) + $0.backgroundColor = UIColor(red: 221/255, green: 222/255, blue: 227/255, alpha: 1) + } + + passwordTextField.do { + $0.placeholder = "비밀번호를 입력해주세요" + $0.font = UIFont(name: "Pretendard-SemiBold", size: 14) + $0.backgroundColor = UIColor(red: 221/255, green: 222/255, blue: 227/255, alpha: 1) + } + + nickNameTextField.do { + $0.placeholder = "닉네임을 입력해주세요" + $0.font = UIFont(name: "Pretendard-SemiBold", size: 14) + $0.backgroundColor = UIColor(red: 221/255, green: 222/255, blue: 227/255, alpha: 1) + } + + phoneNumberTextField.do { + $0.placeholder = "전화번호를 입력해주세요" + $0.font = UIFont(name: "Pretendard-SemiBold", size: 14) + $0.backgroundColor = UIColor(red: 221/255, green: 222/255, blue: 227/255, alpha: 1) + } + + signUpButton.do { + $0.backgroundColor = UIColor(red: 255/255, green: 111/255, blue: 15/255, alpha: 1) + $0.setTitle("회원가입하기", for: .normal) + $0.setTitleColor(.white, for: .normal) + $0.titleLabel?.font = UIFont(name: "Pretendard-Bold", size: 18) + } + } + + private func setLayout() { + [titleLabel, idTextField, passwordTextField, nickNameTextField, phoneNumberTextField, signUpButton].forEach { + self.addSubview($0) + } + + titleLabel.snp.makeConstraints { + $0.centerX.equalToSuperview() + $0.top.equalToSuperview().offset(161) + $0.width.equalTo(236) + $0.height.equalTo(44) + } + + idTextField.snp.makeConstraints { + $0.top.equalTo(titleLabel.snp.bottom).offset(71) + $0.horizontalEdges.equalToSuperview().inset(20) + $0.height.equalTo(52) + } + + passwordTextField.snp.makeConstraints { + $0.top.equalTo(idTextField.snp.bottom).offset(7) + $0.horizontalEdges.equalTo(idTextField) + $0.height.equalTo(52) + } + + nickNameTextField.snp.makeConstraints { + $0.top.equalTo(passwordTextField.snp.bottom).offset(7) + $0.horizontalEdges.equalTo(idTextField) + $0.height.equalTo(52) + } + + phoneNumberTextField.snp.makeConstraints { + $0.top.equalTo(nickNameTextField.snp.bottom).offset(7) + $0.horizontalEdges.equalTo(idTextField) + $0.height.equalTo(52) + } + + signUpButton.snp.makeConstraints { + $0.top.equalTo(phoneNumberTextField.snp.bottom).offset(35) + $0.horizontalEdges.equalTo(idTextField) + $0.height.equalTo(58) + } + } +} + + +final class SignUpViewController: UIViewController { + + private let rootView = SignUpView() + + + let userProvider = MoyaProvider( + plugins: [MoyaLoggingPlugin()] + ) + + override func loadView() { + self.view = rootView + } + + override func touchesEnded(_ touches: Set, with event: UIEvent?) { + super.touchesEnded(touches, with: event) + self.view.endEditing(true) + } + + override func viewDidLoad() { + super.viewDidLoad() + + self.view.backgroundColor = .white + setTarget() + } + + private func setTarget() { + rootView.signUpButton.addTarget(self, action: #selector(signUpButtonDidTap), for: .touchUpInside) + } + + @objc private func signUpButtonDidTap() { + guard let id = rootView.idTextField.text else { return } + guard let password = rootView.passwordTextField.text else { return } + guard let nickName = rootView.nickNameTextField.text else { return } + guard let phoneNumber = rootView.phoneNumberTextField.text else { return } + + let request = SignUpRequestModel( + authenticationId: id, + password: password, + nickname: nickName, + phone: phoneNumber + ) + + UserService.shared.signUp(request: request) { [weak self] response in + switch response { + case .success(let data): + guard let data = data as? SignUpResponseModel else { return } + dump(data) + self?.pushToCheckUserInfoVC() + case .requestErr: + print("요청 오류 입니다") + case .decodedErr: + print("디코딩 오류 입니다") + case .pathErr: + print("경로 오류 입니다") + case .serverErr: + print("서버 오류입니다") + case .networkFail: + print("네트워크 오류입니다") + } + } + } + + private func pushToCheckUserInfoVC() { + let checkUserInfoVC = CheckUserInfoViewController() + self.navigationController?.pushViewController(checkUserInfoVC, animated: true) + } +} diff --git a/34th-SOPT-iOS-DanggeunCloneCoding/UserService.swift b/34th-SOPT-iOS-DanggeunCloneCoding/UserService.swift new file mode 100644 index 0000000..106d6c2 --- /dev/null +++ b/34th-SOPT-iOS-DanggeunCloneCoding/UserService.swift @@ -0,0 +1,81 @@ +// +// UserService.swift +// 34th-SOPT-iOS-DanggeunCloneCoding +// +// Created by 김민성 on 2024/04/27. +// + +import Foundation + +import Moya + +final class UserService { + static let shared = UserService() + private var userProvider = MoyaProvider(plugins: [MoyaLoggingPlugin()]) + + private init() {} +} + +extension UserService { + func getUserInfo(memberId: String, completion: @escaping (NetworkResult) -> Void) { + userProvider.request(.getUserInfo(memberId: memberId)) { result in + switch result { + case .success(let response): + let statusCode = response.statusCode + let data = response.data + + let networkResult = self.judgeStatus(by: statusCode, data, UserInfoResponseModel.self) + completion(networkResult) + + case .failure: + completion(.networkFail) + } + } + } + + + func signUp(request: SignUpRequestModel, completion: @escaping (NetworkResult) -> Void) { + userProvider.request(.signUp(request: request)) { result in + switch result { + case .success(let response): + print("🫶 memberID는 \(String(describing: response.response?.allHeaderFields["Location"]))") + + let statusCode = response.statusCode + let data = response.data + + let networkResult = self.judgeStatus(by: statusCode, data, SignUpResponseModel.self) + completion(networkResult) + + case .failure: + completion(.networkFail) + } + } + } + + + + + public func judgeStatus(by statusCode: Int, _ data: Data, _ object: T.Type) -> NetworkResult { + + switch statusCode { + case 200..<205: + return isValidData(data: data, T.self) + case 400..<500: + return .requestErr + case 500: + return .serverErr + default: + return .networkFail + } + } + + + private func isValidData(data: Data, _ object: T.Type) -> NetworkResult { + let decoder = JSONDecoder() + guard let decodedData = try? decoder.decode(T.self, from: data) else { + print("⛔️ \(self)애서 디코딩 오류가 발생했습니다 ⛔️") + return .pathErr } + + return .success(decodedData as Any) + } +} diff --git a/34th-SOPT-iOS-DanggeunCloneCoding/UserTargetType.swift b/34th-SOPT-iOS-DanggeunCloneCoding/UserTargetType.swift new file mode 100644 index 0000000..0ffd317 --- /dev/null +++ b/34th-SOPT-iOS-DanggeunCloneCoding/UserTargetType.swift @@ -0,0 +1,61 @@ +// +// UserTargetType.swift +// 34th-SOPT-iOS-DanggeunCloneCoding +// +// Created by 김민성 on 2024/04/27. +// + +import Foundation + +import Moya + + +enum UserTargetType { + case getUserInfo(memberId: String) + case signUp(request: SignUpRequestModel) +} + +extension UserTargetType: TargetType { + var baseURL: URL { + return URL(string: Config.baseURL)! + } + + var path: String { + switch self { + case .signUp: + return "/member/join" + case .getUserInfo(memberId: let memberId): + return "/member/info" + } + } + + var method: Moya.Method { + switch self { + case .signUp: + return .post + case .getUserInfo: + return .get + } + } + + var task: Moya.Task { + switch self { + case .signUp(let request): + return .requestJSONEncodable(request) + case .getUserInfo: + return .requestPlain + } + } + + var headers: [String : String]? { + switch self { + case.signUp: + return ["Content-Type": "application/json"] + case .getUserInfo(let memberId): + return ["Content-Type": "application/json", + "memberId" : memberId] + } + + } +} +