diff --git a/30th-Assignment/30th-Assignment/30th-Assignment.xcodeproj/project.pbxproj b/30th-Assignment/30th-Assignment/30th-Assignment.xcodeproj/project.pbxproj index cda97a2..6a45e6c 100644 --- a/30th-Assignment/30th-Assignment/30th-Assignment.xcodeproj/project.pbxproj +++ b/30th-Assignment/30th-Assignment/30th-Assignment.xcodeproj/project.pbxproj @@ -7,10 +7,30 @@ objects = { /* Begin PBXBuildFile section */ + 4D153CA32835DA150072B731 /* AppCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D153CA22835DA150072B731 /* AppCoordinator.swift */; }; + 4D153CA52835DA290072B731 /* LoginCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D153CA42835DA290072B731 /* LoginCoordinator.swift */; }; + 4D153CA72835DA570072B731 /* FeedCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D153CA62835DA570072B731 /* FeedCoordinator.swift */; }; + 4D153CA92835DDB80072B731 /* MakeNameCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D153CA82835DDB80072B731 /* MakeNameCoordinator.swift */; }; + 4D153CAD2835DE570072B731 /* Coordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D153CAC2835DE570072B731 /* Coordinator.swift */; }; + 4D153CAF2835DE750072B731 /* TransitionModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D153CAE2835DE750072B731 /* TransitionModel.swift */; }; + 4D153CB1283665460072B731 /* MakePasswordCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D153CB0283665460072B731 /* MakePasswordCoordinator.swift */; }; + 4D153CB32836694E0072B731 /* CompleteLoginCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D153CB22836694E0072B731 /* CompleteLoginCoordinator.swift */; }; 4D22C0C927FB74FD005F3A73 /* InstaButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D22C0C827FB74FD005F3A73 /* InstaButton.swift */; }; 4D22C0CB27FB8A5E005F3A73 /* MakeNameViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D22C0CA27FB8A5E005F3A73 /* MakeNameViewController.swift */; }; 4D22C0CD27FB8C6C005F3A73 /* MakePasswordViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D22C0CC27FB8C6C005F3A73 /* MakePasswordViewController.swift */; }; 4D22C0CF27FB8D06005F3A73 /* CompleteLoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D22C0CE27FB8D06005F3A73 /* CompleteLoginViewController.swift */; }; + 4D32E26428328DD500A3DD31 /* APIEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D32E26328328DD500A3DD31 /* APIEnvironment.swift */; }; + 4D32E26628328DE300A3DD31 /* NetworkResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D32E26528328DE300A3DD31 /* NetworkResult.swift */; }; + 4D32E26828328DF900A3DD31 /* BaseRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D32E26728328DF900A3DD31 /* BaseRouter.swift */; }; + 4D32E26A28328E0E00A3DD31 /* NetworkConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D32E26928328E0E00A3DD31 /* NetworkConstants.swift */; }; + 4D32E26C28328E1F00A3DD31 /* BaseService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D32E26B28328E1F00A3DD31 /* BaseService.swift */; }; + 4D32E26E28328F2900A3DD31 /* GenaralResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D32E26D28328F2900A3DD31 /* GenaralResponse.swift */; }; + 4D32E27028328FB400A3DD31 /* AuthRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D32E26F28328FB400A3DD31 /* AuthRouter.swift */; }; + 4D32E27228328FC300A3DD31 /* AuthService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D32E27128328FC300A3DD31 /* AuthService.swift */; }; + 4D32E2742832951900A3DD31 /* URLConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D32E2732832951900A3DD31 /* URLConstants.swift */; }; + 4D32E2762832961400A3DD31 /* Auth.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D32E2752832961300A3DD31 /* Auth.swift */; }; + 4D32E27C28329E0C00A3DD31 /* Alamofire in Frameworks */ = {isa = PBXBuildFile; productRef = 4D32E27B28329E0C00A3DD31 /* Alamofire */; }; + 4D32E27E2832B25500A3DD31 /* UIViewController+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D32E27D2832B25500A3DD31 /* UIViewController+.swift */; }; 4DD40B2328136965006CABC1 /* BackButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4DD40B2228136965006CABC1 /* BackButton.swift */; }; 4DD40B26281379D7006CABC1 /* TabBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4DD40B25281379D7006CABC1 /* TabBarController.swift */; }; 4DD40B2E28137A8B006CABC1 /* FeedViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4DD40B2D28137A8B006CABC1 /* FeedViewController.swift */; }; @@ -32,10 +52,29 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 4D153CA22835DA150072B731 /* AppCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppCoordinator.swift; sourceTree = ""; }; + 4D153CA42835DA290072B731 /* LoginCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginCoordinator.swift; sourceTree = ""; }; + 4D153CA62835DA570072B731 /* FeedCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedCoordinator.swift; sourceTree = ""; }; + 4D153CA82835DDB80072B731 /* MakeNameCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MakeNameCoordinator.swift; sourceTree = ""; }; + 4D153CAC2835DE570072B731 /* Coordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Coordinator.swift; sourceTree = ""; }; + 4D153CAE2835DE750072B731 /* TransitionModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransitionModel.swift; sourceTree = ""; }; + 4D153CB0283665460072B731 /* MakePasswordCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MakePasswordCoordinator.swift; sourceTree = ""; }; + 4D153CB22836694E0072B731 /* CompleteLoginCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompleteLoginCoordinator.swift; sourceTree = ""; }; 4D22C0C827FB74FD005F3A73 /* InstaButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstaButton.swift; sourceTree = ""; }; 4D22C0CA27FB8A5E005F3A73 /* MakeNameViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MakeNameViewController.swift; sourceTree = ""; }; 4D22C0CC27FB8C6C005F3A73 /* MakePasswordViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MakePasswordViewController.swift; sourceTree = ""; }; 4D22C0CE27FB8D06005F3A73 /* CompleteLoginViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompleteLoginViewController.swift; sourceTree = ""; }; + 4D32E26328328DD500A3DD31 /* APIEnvironment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIEnvironment.swift; sourceTree = ""; }; + 4D32E26528328DE300A3DD31 /* NetworkResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkResult.swift; sourceTree = ""; }; + 4D32E26728328DF900A3DD31 /* BaseRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseRouter.swift; sourceTree = ""; }; + 4D32E26928328E0E00A3DD31 /* NetworkConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkConstants.swift; sourceTree = ""; }; + 4D32E26B28328E1F00A3DD31 /* BaseService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseService.swift; sourceTree = ""; }; + 4D32E26D28328F2900A3DD31 /* GenaralResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenaralResponse.swift; sourceTree = ""; }; + 4D32E26F28328FB400A3DD31 /* AuthRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthRouter.swift; sourceTree = ""; }; + 4D32E27128328FC300A3DD31 /* AuthService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthService.swift; sourceTree = ""; }; + 4D32E2732832951900A3DD31 /* URLConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLConstants.swift; sourceTree = ""; }; + 4D32E2752832961300A3DD31 /* Auth.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Auth.swift; sourceTree = ""; }; + 4D32E27D2832B25500A3DD31 /* UIViewController+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+.swift"; sourceTree = ""; }; 4DD40B2228136965006CABC1 /* BackButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackButton.swift; sourceTree = ""; }; 4DD40B25281379D7006CABC1 /* TabBarController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBarController.swift; sourceTree = ""; }; 4DD40B2D28137A8B006CABC1 /* FeedViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedViewController.swift; sourceTree = ""; }; @@ -61,6 +100,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 4D32E27C28329E0C00A3DD31 /* Alamofire in Frameworks */, 4DD4DBB327FA1FDA00E54124 /* Then in Frameworks */, 4DD4DBB627FA1FEE00E54124 /* SnapKit in Frameworks */, ); @@ -69,6 +109,102 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 4D153C9F2835C1F10072B731 /* APIConstants */ = { + isa = PBXGroup; + children = ( + 4D32E26928328E0E00A3DD31 /* NetworkConstants.swift */, + 4D32E2732832951900A3DD31 /* URLConstants.swift */, + ); + path = APIConstants; + sourceTree = ""; + }; + 4D153CA02835D9870072B731 /* Coordinator */ = { + isa = PBXGroup; + children = ( + 4D153CA22835DA150072B731 /* AppCoordinator.swift */, + 4D153CA12835DA000072B731 /* Foundation */, + 4D153CAA2835DDBF0072B731 /* AuthCoordinators */, + 4D153CAB2835DDEA0072B731 /* TabBarCoordinators */, + ); + path = Coordinator; + sourceTree = ""; + }; + 4D153CA12835DA000072B731 /* Foundation */ = { + isa = PBXGroup; + children = ( + 4D153CAC2835DE570072B731 /* Coordinator.swift */, + 4D153CAE2835DE750072B731 /* TransitionModel.swift */, + ); + path = Foundation; + sourceTree = ""; + }; + 4D153CAA2835DDBF0072B731 /* AuthCoordinators */ = { + isa = PBXGroup; + children = ( + 4D153CA42835DA290072B731 /* LoginCoordinator.swift */, + 4D153CA82835DDB80072B731 /* MakeNameCoordinator.swift */, + 4D153CB0283665460072B731 /* MakePasswordCoordinator.swift */, + 4D153CB22836694E0072B731 /* CompleteLoginCoordinator.swift */, + ); + path = AuthCoordinators; + sourceTree = ""; + }; + 4D153CAB2835DDEA0072B731 /* TabBarCoordinators */ = { + isa = PBXGroup; + children = ( + 4D153CA62835DA570072B731 /* FeedCoordinator.swift */, + ); + path = TabBarCoordinators; + sourceTree = ""; + }; + 4D32E25E28328D1A00A3DD31 /* Network */ = { + isa = PBXGroup; + children = ( + 4D32E25F28328D5900A3DD31 /* Foundation */, + 4D32E26028328D6C00A3DD31 /* Model */, + 4D32E26128328D7300A3DD31 /* Router */, + 4D32E26228328D9D00A3DD31 /* Service */, + ); + path = Network; + sourceTree = ""; + }; + 4D32E25F28328D5900A3DD31 /* Foundation */ = { + isa = PBXGroup; + children = ( + 4D153C9F2835C1F10072B731 /* APIConstants */, + 4D32E26328328DD500A3DD31 /* APIEnvironment.swift */, + 4D32E26528328DE300A3DD31 /* NetworkResult.swift */, + 4D32E26728328DF900A3DD31 /* BaseRouter.swift */, + 4D32E26B28328E1F00A3DD31 /* BaseService.swift */, + ); + path = Foundation; + sourceTree = ""; + }; + 4D32E26028328D6C00A3DD31 /* Model */ = { + isa = PBXGroup; + children = ( + 4D32E26D28328F2900A3DD31 /* GenaralResponse.swift */, + 4D32E2752832961300A3DD31 /* Auth.swift */, + ); + path = Model; + sourceTree = ""; + }; + 4D32E26128328D7300A3DD31 /* Router */ = { + isa = PBXGroup; + children = ( + 4D32E26F28328FB400A3DD31 /* AuthRouter.swift */, + ); + path = Router; + sourceTree = ""; + }; + 4D32E26228328D9D00A3DD31 /* Service */ = { + isa = PBXGroup; + children = ( + 4D32E27128328FC300A3DD31 /* AuthService.swift */, + ); + path = Service; + sourceTree = ""; + }; 4DD40B24281379AB006CABC1 /* Tabbar */ = { isa = PBXGroup; children = ( @@ -136,6 +272,7 @@ 4DD4DB9227FA171D00E54124 /* 30th-Assignment */ = { isa = PBXGroup; children = ( + 4D32E25E28328D1A00A3DD31 /* Network */, 4DD4DBA727FA17E200E54124 /* Global */, 4DD4DBA827FA17F200E54124 /* Screens */, ); @@ -159,6 +296,7 @@ 4DD4DBA827FA17F200E54124 /* Screens */ = { isa = PBXGroup; children = ( + 4D153CA02835D9870072B731 /* Coordinator */, 4DD40B24281379AB006CABC1 /* Tabbar */, 4DD40B2C28137A6D006CABC1 /* Feed */, 4DD40B2B28137A65006CABC1 /* Search */, @@ -189,6 +327,7 @@ isa = PBXGroup; children = ( 4DD4DBBE27FA43AC00E54124 /* UIView+.swift */, + 4D32E27D2832B25500A3DD31 /* UIViewController+.swift */, ); path = Extension; sourceTree = ""; @@ -260,6 +399,7 @@ packageProductDependencies = ( 4DD4DBB227FA1FDA00E54124 /* Then */, 4DD4DBB527FA1FEE00E54124 /* SnapKit */, + 4D32E27B28329E0C00A3DD31 /* Alamofire */, ); productName = "30th-Assignment"; productReference = 4DD4DB9027FA171D00E54124 /* 30th-Assignment.app */; @@ -292,6 +432,7 @@ packageReferences = ( 4DD4DBB127FA1FDA00E54124 /* XCRemoteSwiftPackageReference "Then" */, 4DD4DBB427FA1FEE00E54124 /* XCRemoteSwiftPackageReference "SnapKit" */, + 4D32E27A28329E0C00A3DD31 /* XCRemoteSwiftPackageReference "Alamofire" */, ); productRefGroup = 4DD4DB9127FA171D00E54124 /* Products */; projectDirPath = ""; @@ -320,21 +461,40 @@ buildActionMask = 2147483647; files = ( 4DD40B3028137A9A006CABC1 /* SearchViewController.swift in Sources */, + 4D32E26E28328F2900A3DD31 /* GenaralResponse.swift in Sources */, 4DD4DBBF27FA43AC00E54124 /* UIView+.swift in Sources */, + 4D153CAF2835DE750072B731 /* TransitionModel.swift in Sources */, + 4D32E26A28328E0E00A3DD31 /* NetworkConstants.swift in Sources */, + 4D153CB1283665460072B731 /* MakePasswordCoordinator.swift in Sources */, 4DD40B26281379D7006CABC1 /* TabBarController.swift in Sources */, 4D22C0C927FB74FD005F3A73 /* InstaButton.swift in Sources */, + 4D32E26428328DD500A3DD31 /* APIEnvironment.swift in Sources */, 4DD40B3428137ABA006CABC1 /* ShopViewController.swift in Sources */, + 4D153CA72835DA570072B731 /* FeedCoordinator.swift in Sources */, + 4D153CB32836694E0072B731 /* CompleteLoginCoordinator.swift in Sources */, 4D22C0CB27FB8A5E005F3A73 /* MakeNameViewController.swift in Sources */, + 4D153CAD2835DE570072B731 /* Coordinator.swift in Sources */, 4DD40B3628137AC4006CABC1 /* ProfileViewController.swift in Sources */, + 4D32E26828328DF900A3DD31 /* BaseRouter.swift in Sources */, + 4D32E2762832961400A3DD31 /* Auth.swift in Sources */, + 4D153CA32835DA150072B731 /* AppCoordinator.swift in Sources */, + 4D32E26C28328E1F00A3DD31 /* BaseService.swift in Sources */, 4DF86F5827FF72DB009DCFAA /* ImageLiteral.swift in Sources */, + 4D153CA52835DA290072B731 /* LoginCoordinator.swift in Sources */, 4D22C0CF27FB8D06005F3A73 /* CompleteLoginViewController.swift in Sources */, 4DD40B2E28137A8B006CABC1 /* FeedViewController.swift in Sources */, + 4D32E26628328DE300A3DD31 /* NetworkResult.swift in Sources */, 4DD4DBBB27FA3A0600E54124 /* BaseViewController.swift in Sources */, 4DD4DBBD27FA3E6100E54124 /* InstaTextField.swift in Sources */, 4DD4DBB927FA39F800E54124 /* LoginViewController.swift in Sources */, + 4D32E27228328FC300A3DD31 /* AuthService.swift in Sources */, 4DD40B2328136965006CABC1 /* BackButton.swift in Sources */, 4D22C0CD27FB8C6C005F3A73 /* MakePasswordViewController.swift in Sources */, 4DD4DB9427FA171D00E54124 /* AppDelegate.swift in Sources */, + 4D32E27E2832B25500A3DD31 /* UIViewController+.swift in Sources */, + 4D32E27028328FB400A3DD31 /* AuthRouter.swift in Sources */, + 4D153CA92835DDB80072B731 /* MakeNameCoordinator.swift in Sources */, + 4D32E2742832951900A3DD31 /* URLConstants.swift in Sources */, 4DD40B3228137AAD006CABC1 /* ReelsViewController.swift in Sources */, 4DD4DB9627FA171D00E54124 /* SceneDelegate.swift in Sources */, ); @@ -546,6 +706,14 @@ /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ + 4D32E27A28329E0C00A3DD31 /* XCRemoteSwiftPackageReference "Alamofire" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/Alamofire/Alamofire"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 5.0.0; + }; + }; 4DD4DBB127FA1FDA00E54124 /* XCRemoteSwiftPackageReference "Then" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/devxoul/Then"; @@ -565,6 +733,11 @@ /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ + 4D32E27B28329E0C00A3DD31 /* Alamofire */ = { + isa = XCSwiftPackageProductDependency; + package = 4D32E27A28329E0C00A3DD31 /* XCRemoteSwiftPackageReference "Alamofire" */; + productName = Alamofire; + }; 4DD4DBB227FA1FDA00E54124 /* Then */ = { isa = XCSwiftPackageProductDependency; package = 4DD4DBB127FA1FDA00E54124 /* XCRemoteSwiftPackageReference "Then" */; diff --git a/30th-Assignment/30th-Assignment/30th-Assignment.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/30th-Assignment/30th-Assignment/30th-Assignment.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 68a47e6..e27fc7e 100644 --- a/30th-Assignment/30th-Assignment/30th-Assignment.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/30th-Assignment/30th-Assignment/30th-Assignment.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,14 @@ { "pins" : [ + { + "identity" : "alamofire", + "kind" : "remoteSourceControl", + "location" : "https://github.com/Alamofire/Alamofire", + "state" : { + "revision" : "354dda32d89fc8cd4f5c46487f64957d355f53d8", + "version" : "5.6.1" + } + }, { "identity" : "snapkit", "kind" : "remoteSourceControl", diff --git a/30th-Assignment/30th-Assignment/30th-Assignment/Global/Extension/UIViewController+.swift b/30th-Assignment/30th-Assignment/30th-Assignment/Global/Extension/UIViewController+.swift new file mode 100644 index 0000000..543c9d8 --- /dev/null +++ b/30th-Assignment/30th-Assignment/30th-Assignment/Global/Extension/UIViewController+.swift @@ -0,0 +1,59 @@ +// +// UIViewController+.swift +// 30th-Assignment +// +// Created by 김수연 on 2022/05/17. +// + +import UIKit + +extension UIViewController { + + /// 확인 버튼 1개, 취소 버튼 1개 Alert 메서드 + func makeAlertWithCancel(okTitle: String, okStyle: UIAlertAction.Style = .default, + cancelTitle: String = "취소", + okAction : ((UIAlertAction) -> Void)?, cancelAction : ((UIAlertAction) -> Void)? = nil, + completion : (() -> Void)? = nil) { + + let generator = UIImpactFeedbackGenerator(style: .medium) + generator.impactOccurred() + + let alertViewController = UIAlertController(title: nil, message: nil, + preferredStyle: .actionSheet) + + let okAction = UIAlertAction(title: okTitle, style: okStyle, handler: okAction) + alertViewController.addAction(okAction) + + let cancelAction = UIAlertAction(title: cancelTitle, style: .cancel, handler: cancelAction) + alertViewController.addAction(cancelAction) + + self.present(alertViewController, animated: true, completion: completion) + } + + /// 확인 버튼 Alert 메서드 + func makeAlert(title : String, message : String? = nil, + okTitle: String = "확인", okAction : ((UIAlertAction) -> Void)? = nil, + completion : (() -> Void)? = nil) { + let generator = UIImpactFeedbackGenerator(style: .medium) + generator.impactOccurred() + let alertViewController = UIAlertController(title: title, message: message, + preferredStyle: .alert) + let okAction = UIAlertAction(title: okTitle, style: .default, handler: okAction) + alertViewController.addAction(okAction) + self.present(alertViewController, animated: true, completion: completion) + } + + /// 확인 버튼 누르면 화면전환되는 Alert 메서드 + func makePresentAlert(title : String, message : String? = nil, + okTitle: String = "확인", nextVC: UIViewController) { + let generator = UIImpactFeedbackGenerator(style: .medium) + generator.impactOccurred() + let alertViewController = UIAlertController(title: title, message: message, + preferredStyle: .alert) + alertViewController.addAction(UIAlertAction(title: okTitle, style: .default) { action in + nextVC.modalPresentationStyle = .fullScreen + self.present(nextVC, animated: true) + }) + self.present(alertViewController, animated: true, completion: nil) + } +} diff --git a/30th-Assignment/30th-Assignment/30th-Assignment/Global/Supports/Info.plist b/30th-Assignment/30th-Assignment/30th-Assignment/Global/Supports/Info.plist index 0eb786d..bc240fd 100644 --- a/30th-Assignment/30th-Assignment/30th-Assignment/Global/Supports/Info.plist +++ b/30th-Assignment/30th-Assignment/30th-Assignment/Global/Supports/Info.plist @@ -2,6 +2,11 @@ + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + UIApplicationSceneManifest UIApplicationSupportsMultipleScenes diff --git a/30th-Assignment/30th-Assignment/30th-Assignment/Global/Supports/SceneDelegate.swift b/30th-Assignment/30th-Assignment/30th-Assignment/Global/Supports/SceneDelegate.swift index f9aa757..390fe2d 100644 --- a/30th-Assignment/30th-Assignment/30th-Assignment/Global/Supports/SceneDelegate.swift +++ b/30th-Assignment/30th-Assignment/30th-Assignment/Global/Supports/SceneDelegate.swift @@ -13,15 +13,22 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { - guard let windowScene = (scene as? UIWindowScene) else { return } +// guard let windowScene = (scene as? UIWindowScene) else { return } +// +// let rootViewController = LoginViewController() +// let navigationController = UINavigationController(rootViewController: rootViewController) +// let window = UIWindow(windowScene: windowScene) +// window.rootViewController = navigationController +// self.window = window +// window.backgroundColor = .white +// window.makeKeyAndVisible() - let rootViewController = LoginViewController() - let navigationController = UINavigationController(rootViewController: rootViewController) + guard let windowScene = (scene as? UIWindowScene) else { return } let window = UIWindow(windowScene: windowScene) - window.rootViewController = navigationController self.window = window - window.backgroundColor = .white - window.makeKeyAndVisible() + + let coordinator = AppCoordinator(window: window) + coordinator.start() } func sceneDidDisconnect(_ scene: UIScene) { diff --git a/30th-Assignment/30th-Assignment/30th-Assignment/Network/Foundation/APIConstants/NetworkConstants.swift b/30th-Assignment/30th-Assignment/30th-Assignment/Network/Foundation/APIConstants/NetworkConstants.swift new file mode 100644 index 0000000..16076a7 --- /dev/null +++ b/30th-Assignment/30th-Assignment/30th-Assignment/Network/Foundation/APIConstants/NetworkConstants.swift @@ -0,0 +1,32 @@ +// +// NetworkConstants.swift +// 30th-Assignment +// +// Created by 김수연 on 2022/05/16. +// + +import Foundation +import Alamofire + +/* + NetworkConstants : 서버통신과정에서 필요한 상수들을 관리 -> header 관련 상수들 + */ + +enum HeaderType { + case basic + case auth + case multiPart + case multiPartWithAuth +} + +enum HTTPHeaderField: String { + case authentication = "Authorization" + case contentType = "Content-Type" + case accesstoken = "accesstoken" +} + +enum ContentType: String { + case json = "Application/json" + case tokenSerial = "" + case multiPart = "multipart/form-data" +} diff --git a/30th-Assignment/30th-Assignment/30th-Assignment/Network/Foundation/APIConstants/URLConstants.swift b/30th-Assignment/30th-Assignment/30th-Assignment/Network/Foundation/APIConstants/URLConstants.swift new file mode 100644 index 0000000..359aa4f --- /dev/null +++ b/30th-Assignment/30th-Assignment/30th-Assignment/Network/Foundation/APIConstants/URLConstants.swift @@ -0,0 +1,18 @@ +// +// URLConstants.swift +// 30th-Assignment +// +// Created by 김수연 on 2022/05/16. +// + +import Foundation + +/* + URLConstants : 각 API 별 URL상수 관리 + */ + +struct URLConstants { + + static let signUp = "/auth/signup" + static let signIn = "/auth/signin" +} diff --git a/30th-Assignment/30th-Assignment/30th-Assignment/Network/Foundation/APIEnvironment.swift b/30th-Assignment/30th-Assignment/30th-Assignment/Network/Foundation/APIEnvironment.swift new file mode 100644 index 0000000..851f9d2 --- /dev/null +++ b/30th-Assignment/30th-Assignment/30th-Assignment/Network/Foundation/APIEnvironment.swift @@ -0,0 +1,28 @@ +// +// APIEnvironment.swift +// 30th-Assignment +// +// Created by 김수연 on 2022/05/16. +// + +import Foundation + +/* + APIEnvironment: baseURL 및 토큰 관리, 추후 배포를 고려하여 development와 production 분리 + */ + +enum APIEnvironment: String, CaseIterable { + case development + case production +} + +extension APIEnvironment { + var baseUrl: String { + switch self { + case .development: + return "http://13.124.62.236" + case .production: + return "" + } + } +} diff --git a/30th-Assignment/30th-Assignment/30th-Assignment/Network/Foundation/BaseRouter.swift b/30th-Assignment/30th-Assignment/30th-Assignment/Network/Foundation/BaseRouter.swift new file mode 100644 index 0000000..43fbf49 --- /dev/null +++ b/30th-Assignment/30th-Assignment/30th-Assignment/Network/Foundation/BaseRouter.swift @@ -0,0 +1,99 @@ +// +// NeworkRequest.swift +// 30th-Assignment +// +// Created by 김수연 on 2022/05/16. +// + +import Foundation +import Alamofire + +/* + BaseRouter : URLRequestConvertible을 채택한 프로토콜, 실제 각 Endpont에 해당하는 Router들이 + BaseRouter를 채택하여 request 과정을 모듈화. + */ + +protocol BaseRouter: URLRequestConvertible { + var baseURL: String { get } + var method: HTTPMethod { get } + var path: String { get } + var parameters: RequestParams { get } + var header: HeaderType { get } +} + +extension BaseRouter { + var baseURL: String { + return APIEnvironment.development.baseUrl + } + + // URLRequestConvertible 구현 + func asURLRequest() throws -> URLRequest { + let url = try baseURL.asURL() + var urlRequest = try URLRequest(url: url.appendingPathComponent(path), method: method) + + urlRequest = self.makeHeaderForRequest(to: urlRequest) + + return try self.makeParameterForRequest(to: urlRequest, with: url) + } + + private func makeHeaderForRequest(to request: URLRequest) -> URLRequest { + var request = request + + switch header { + case .basic: + request.setValue(ContentType.json.rawValue, forHTTPHeaderField: HTTPHeaderField.contentType.rawValue) + + case .auth: + request.setValue(ContentType.json.rawValue, forHTTPHeaderField: HTTPHeaderField.contentType.rawValue) + request.setValue(ContentType.tokenSerial.rawValue, forHTTPHeaderField: HTTPHeaderField.accesstoken.rawValue) + + case .multiPart: + request.setValue(ContentType.multiPart.rawValue, forHTTPHeaderField: HTTPHeaderField.contentType.rawValue) + + case .multiPartWithAuth: + request.setValue(ContentType.multiPart.rawValue, forHTTPHeaderField: HTTPHeaderField.contentType.rawValue) + request.setValue(ContentType.tokenSerial.rawValue, forHTTPHeaderField: HTTPHeaderField.accesstoken.rawValue) + } + + return request + } + + private func makeParameterForRequest(to request: URLRequest, with url: URL) throws -> URLRequest { + var request = request + + switch parameters { + case .query(let query): + let params = query?.toDictionary() ?? [:] + let queryParams = params.map { URLQueryItem(name: $0.key, value: "\($0.value)") } + var components = URLComponents(string: url.appendingPathComponent(path).absoluteString) + components?.queryItems = queryParams + request.url = components?.url + + case .body(let body): + let params = body?.toDictionary() ?? [:] + request.httpBody = try JSONSerialization.data(withJSONObject: params, options: []) + + case .requestParameters(let requestParams): + let params = requestParams + request.httpBody = try JSONSerialization.data(withJSONObject: params, options: []) + } + + return request + } +} + +enum RequestParams { + case query(_ parameter: Codable?) + case body(_ parameter: Codable?) + case requestParameters(_ parameter: [String : Any]) +} + +extension Encodable { + func toDictionary() -> [String: Any] { + guard let data = try? JSONEncoder().encode(self), + let jsonData = try? JSONSerialization.jsonObject(with: data), + let dictionaryData = jsonData as? [String: Any] else { return [:] } + return dictionaryData + } +} + diff --git a/30th-Assignment/30th-Assignment/30th-Assignment/Network/Foundation/BaseService.swift b/30th-Assignment/30th-Assignment/30th-Assignment/Network/Foundation/BaseService.swift new file mode 100644 index 0000000..c1c6d35 --- /dev/null +++ b/30th-Assignment/30th-Assignment/30th-Assignment/Network/Foundation/BaseService.swift @@ -0,0 +1,47 @@ +// +// GeneralResponse.swift +// 30th-Assignment +// +// Created by 김수연 on 2022/05/16. +// + +import Foundation + +import Alamofire + +class BaseService { + + enum TimeOut { + static let requestTimeOut: Float = 30 + static let resourceTimeOut: Float = 30 + } + + let AFmanager: Session = { + var session = AF + let configuration = URLSessionConfiguration.af.default + configuration.timeoutIntervalForRequest = TimeInterval(TimeOut.requestTimeOut) + configuration.timeoutIntervalForResource = TimeInterval(TimeOut.resourceTimeOut) + + session = Session(configuration: configuration) + return session + }() + + func judgeStatus(by statusCode: Int, _ data: Data, _ type: T.Type) -> NetworkResult { + let decoder = JSONDecoder() + guard let decodedData = try? decoder.decode(GeneralResponse.self, from: data) + else { return .pathErr } + print(decodedData) + switch statusCode { + case 200: + return .success(decodedData.data ?? "None-Data") + case 201..<300: + return .success(decodedData.status) + case 400..<500: + return .requestErr(decodedData.status) + case 500: + return .serverErr + default: + return .networkFail + } + } +} diff --git a/30th-Assignment/30th-Assignment/30th-Assignment/Network/Foundation/NetworkResult.swift b/30th-Assignment/30th-Assignment/30th-Assignment/Network/Foundation/NetworkResult.swift new file mode 100644 index 0000000..4440064 --- /dev/null +++ b/30th-Assignment/30th-Assignment/30th-Assignment/Network/Foundation/NetworkResult.swift @@ -0,0 +1,20 @@ +// +// NetworkResult.swift +// 30th-Assignment +// +// Created by 김수연 on 2022/05/16. +// + +import Foundation + +/* + NetworkResult : 네트워크 결과 나누기 + */ + +enum NetworkResult { + case success(T) + case requestErr(T) + case pathErr + case serverErr + case networkFail +} diff --git a/30th-Assignment/30th-Assignment/30th-Assignment/Network/Model/Auth.swift b/30th-Assignment/30th-Assignment/30th-Assignment/Network/Model/Auth.swift new file mode 100644 index 0000000..d5e34cb --- /dev/null +++ b/30th-Assignment/30th-Assignment/30th-Assignment/Network/Model/Auth.swift @@ -0,0 +1,16 @@ +// +// Auth.swift +// 30th-Assignment +// +// Created by 김수연 on 2022/05/16. +// + +import Foundation + +struct SignIn: Codable { + let name, email: String +} + +struct SignUp: Codable { + let id: Int +} diff --git a/30th-Assignment/30th-Assignment/30th-Assignment/Network/Model/GenaralResponse.swift b/30th-Assignment/30th-Assignment/30th-Assignment/Network/Model/GenaralResponse.swift new file mode 100644 index 0000000..256a422 --- /dev/null +++ b/30th-Assignment/30th-Assignment/30th-Assignment/Network/Model/GenaralResponse.swift @@ -0,0 +1,30 @@ +// +// GenaralResponse.swift +// 30th-Assignment +// +// Created by 김수연 on 2022/05/16. +// + +import Foundation + +struct GeneralResponse: Codable { + var status: Int + var success: Bool? + var message: String? + var data: T? + + enum CodingKeys: String, CodingKey { + case status = "status" + case success = "success" + case message = "message" + case data = "data" + } + + init(from decoder: Decoder) throws { + let values = try decoder.container(keyedBy: CodingKeys.self) + status = (try? values.decode(Int.self, forKey: .status)) ?? -1 + success = (try? values.decode(Bool.self, forKey: .success)) ?? false + message = (try? values.decode(String.self, forKey: .message)) ?? "" + data = (try? values.decode(T.self, forKey: .data)) ?? nil + } +} diff --git a/30th-Assignment/30th-Assignment/30th-Assignment/Network/Router/AuthRouter.swift b/30th-Assignment/30th-Assignment/30th-Assignment/Network/Router/AuthRouter.swift new file mode 100644 index 0000000..7dec13f --- /dev/null +++ b/30th-Assignment/30th-Assignment/30th-Assignment/Network/Router/AuthRouter.swift @@ -0,0 +1,63 @@ +// +// AuthRouter.swift +// 30th-Assignment +// +// Created by 김수연 on 2022/05/16. +// + +import Foundation +import Alamofire + +/* + AuthRouter : 여러 Endpoint들을 갖고 있는 enum + BaseRouter를 채택해서 path, method, header, parameter를 각 라우터에 맞게 request를 만든다. + */ + +enum AuthRouter { + case signIn(email: String, pw: String) + case signUp(email: String, name: String, pw: String) +} + +extension AuthRouter: BaseRouter { + + var path: String { + switch self { + case .signIn: + return URLConstants.signIn + case .signUp: + return URLConstants.signUp + } + } + + var method: HTTPMethod { + switch self { + case .signIn, .signUp: + return .post + } + } + + var header: HeaderType { + switch self { + case .signIn, .signUp: + return .basic + } + } + + var parameters: RequestParams { + switch self { + case .signUp(let email, let name, let pw): + let body: [String:Any] = [ + "email": email, + "name": name, + "password":pw + ] + return .requestParameters(body) + case .signIn(let email, let pw): + let body: [String:Any] = [ + "email": email, + "password": pw + ] + return .requestParameters(body) + } + } +} diff --git a/30th-Assignment/30th-Assignment/30th-Assignment/Network/Service/AuthService.swift b/30th-Assignment/30th-Assignment/30th-Assignment/Network/Service/AuthService.swift new file mode 100644 index 0000000..cd45623 --- /dev/null +++ b/30th-Assignment/30th-Assignment/30th-Assignment/Network/Service/AuthService.swift @@ -0,0 +1,58 @@ +// +// AuthService.swift +// 30th-Assignment +// +// Created by 김수연 on 2022/05/16. +// + +import Foundation +import Alamofire + +/* + AuthService : 실제 서버통신을 하기 위해 Service 함수를 구현하는 클래스 + 싱글턴으로 접근한다. 앞서 만든 request 함수를 호출하고 Router를 통해 서버통신 수행 + 네트워크 결과를 받아와서 + */ + +class AuthService: BaseService { + static let shared = AuthService() + + private override init() { } + + /// [POST] 로그인 + func requestSignIn(email: String, pw: String, completion: @escaping (NetworkResult) -> (Void)) { + AFmanager.request(AuthRouter.signIn(email: email, pw: pw)) + .validate(statusCode: 200...500) + .responseData { response in + switch response.result { + case .success: + guard let statusCode = response.response?.statusCode else { return } + guard let data = response.data else { return } + let networkResult = self.judgeStatus(by: statusCode, data, SignIn.self) + + completion(networkResult) + + case .failure(let err): + print(err.localizedDescription) + } + } + } + /// [POST] 회원가입 + func requestSignUp(email: String, name: String, pw: String, completion: @escaping (NetworkResult) -> (Void)) { + AFmanager.request(AuthRouter.signUp(email: email, name: name, pw: pw)) + .validate(statusCode: 200...500) + .responseData { response in + switch response.result { + case .success: + guard let statusCode = response.response?.statusCode else { return } + guard let data = response.data else { return} + let networkResult = self.judgeStatus(by: statusCode, data, SignUp.self) + + completion(networkResult) + + case .failure(let err): + print(err.localizedDescription) + } + } + } +} diff --git a/30th-Assignment/30th-Assignment/30th-Assignment/Screens/Authentication/CompleteLoginViewController.swift b/30th-Assignment/30th-Assignment/30th-Assignment/Screens/Authentication/CompleteLoginViewController.swift index b5099e3..eb483da 100644 --- a/30th-Assignment/30th-Assignment/30th-Assignment/Screens/Authentication/CompleteLoginViewController.swift +++ b/30th-Assignment/30th-Assignment/30th-Assignment/Screens/Authentication/CompleteLoginViewController.swift @@ -12,6 +12,8 @@ import Then final class CompleteLoginViewController: BaseViewController { + private var coordinator: CompleteLoginCoordinator + var userName: String? { didSet { if let userName = userName { @@ -19,6 +21,7 @@ final class CompleteLoginViewController: BaseViewController { } } } + var password: String? private let titleLabel = UILabel().then { $0.text = "땡땡땡님 Instargram에 \n 오신 것을 환영합니다" @@ -38,10 +41,7 @@ final class CompleteLoginViewController: BaseViewController { private lazy var doneButton = InstaButton(title: "완료하기").then { let LoginViewAction = UIAction { _ in - let tabBarController = TabBarController() - - tabBarController.modalPresentationStyle = .fullScreen - self.present(tabBarController, animated: true) + self.postSignUp() } $0.addAction(LoginViewAction, for: .touchUpInside) } @@ -53,10 +53,24 @@ final class CompleteLoginViewController: BaseViewController { $0.font = .systemFont(ofSize: 14, weight: .regular) } + init(coordinator: CompleteLoginCoordinator) { + self.coordinator = coordinator + super.init() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + override func viewDidLoad() { super.viewDidLoad() } + override func viewDidDisappear(_ animated: Bool) { + super.viewDidDisappear(animated) + coordinator.didFinishChildCoordinator() + } + override func configUI() { view.backgroundColor = .white setupBaseNavigationBar() @@ -103,4 +117,38 @@ final class CompleteLoginViewController: BaseViewController { presentingVC.popToRootViewController(animated: true) self.dismiss(animated: true) } + + private func postSignUp() { + guard let name = userName, + let email = userName, + let password = password + else { return } + + AuthService.shared.requestSignUp(email: email, name: name, pw: password) { result in + switch result { + case .success: + self.makeAlert(title: "회원가입 성공") { UIAlertAction in + self.dismiss(animated: true) { + self.coordinator.transitionToLogin() + } + } + case .requestErr(let status): + guard let status = status as? Int else { return } + + switch status { + case 409: + self.makeAlert(title: "동일한 이메일로 생성된 계정이 존재합니다.") + default: + self.makeAlert(title: "아이디와 비밀번호를 확인해주세요.") + } + + case .pathErr: + print("pathErr") + case .serverErr: + print("serverErr") + case .networkFail: + print("networkFail") + } + } + } } diff --git a/30th-Assignment/30th-Assignment/30th-Assignment/Screens/Authentication/LoginViewController.swift b/30th-Assignment/30th-Assignment/30th-Assignment/Screens/Authentication/LoginViewController.swift index f63d90e..f13769e 100644 --- a/30th-Assignment/30th-Assignment/30th-Assignment/Screens/Authentication/LoginViewController.swift +++ b/30th-Assignment/30th-Assignment/30th-Assignment/Screens/Authentication/LoginViewController.swift @@ -12,6 +12,8 @@ import Then final class LoginViewController: BaseViewController { + private var coordinator: LoginCoordinator + private let logoImage = UIImageView().then { $0.image = ImageLiteral.imgInstagramLogo $0.contentMode = .scaleToFill @@ -38,14 +40,10 @@ final class LoginViewController: BaseViewController { private lazy var loginButton = InstaButton(title: "로그인").then { $0.isEnabled = false - let completeViewAction = UIAction { _ in - let completeVC = CompleteLoginViewController() - - completeVC.modalPresentationStyle = .fullScreen - completeVC.userName = self.emailTextField.text - self.present(completeVC, animated: true) + let LoginAction = UIAction { _ in + self.postSignIn() } - $0.addAction(completeViewAction, for: .touchUpInside) + $0.addAction(LoginAction, for: .touchUpInside) } private let signUpLabel = UILabel().then { @@ -60,11 +58,20 @@ final class LoginViewController: BaseViewController { $0.titleLabel?.font = .systemFont(ofSize: 14) let pushSignUpViewAction = UIAction { _ in - self.navigationController?.pushViewController(MakeNameViewController(), animated: true) + self.coordinator.transitionToMakeName() } $0.addAction(pushSignUpViewAction, for: .touchUpInside) } + init(coordinator: LoginCoordinator) { + self.coordinator = coordinator + super.init() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + override func viewDidLoad() { super.viewDidLoad() } @@ -74,6 +81,11 @@ final class LoginViewController: BaseViewController { intialize() } + override func viewDidDisappear(_ animated: Bool) { + super.viewDidDisappear(animated) + coordinator.didFinishChildCoordinator() + } + /// 화면 터치했을 때 텍스트 필드 edit 종료하기 override func touchesBegan(_ touches: Set, with event: UIEvent?) { view.endEditing(true) @@ -143,6 +155,39 @@ final class LoginViewController: BaseViewController { /// 도전과제 (2) loginButton.isEnabled = [emailTextField, passwordTextField].allSatisfy { $0.hasText } } + + private func postSignIn() { + guard let email = emailTextField.text, + let password = passwordTextField.text + else { return } + + AuthService.shared.requestSignIn(email: email, pw: password) { result in + switch result { + case .success: + self.makeAlert(title: "로그인성공") { _ in + self.coordinator.transitionToFeed() + } + case .requestErr(let status): + guard let status = status as? Int else { return } + + switch status { + case 404: + self.makeAlert(title: "이메일에 해당하는 사용자정보가 없습니다.") + case 409: + self.makeAlert(title: "비밀번호가 올바르지 않습니다.") + default: + self.makeAlert(title: "아이디와 비밀번호를 다시 확인해주세요.") + } + + case .pathErr: + print("pathErr") + case .serverErr: + print("serverErr") + case .networkFail: + print("networkFail") + } + } + } } extension LoginViewController: UITextFieldDelegate { diff --git a/30th-Assignment/30th-Assignment/30th-Assignment/Screens/Authentication/MakeNameViewController.swift b/30th-Assignment/30th-Assignment/30th-Assignment/Screens/Authentication/MakeNameViewController.swift index cb2c2d7..b744e78 100644 --- a/30th-Assignment/30th-Assignment/30th-Assignment/Screens/Authentication/MakeNameViewController.swift +++ b/30th-Assignment/30th-Assignment/30th-Assignment/Screens/Authentication/MakeNameViewController.swift @@ -12,6 +12,8 @@ import Then final class MakeNameViewController: BaseViewController { + private var coordinator: MakeNameCoordinator + private let titleLabel = UILabel().then { $0.text = "사용자 이름 만들기" $0.font = .systemFont(ofSize: 24) @@ -36,14 +38,20 @@ final class MakeNameViewController: BaseViewController { $0.isEnabled = false let pushMakePasswordViewAction = UIAction { _ in - let makePasswordVC = MakePasswordViewController() - - makePasswordVC.userName = self.userNameTextField.text ?? "" - self.navigationController?.pushViewController(makePasswordVC, animated: true) + self.coordinator.transitionToMakePassword(userName: self.userNameTextField.text ?? "") } $0.addAction(pushMakePasswordViewAction, for: .touchUpInside) } + init(coordinator: MakeNameCoordinator) { + self.coordinator = coordinator + super.init() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + override func viewDidLoad() { super.viewDidLoad() } @@ -52,6 +60,11 @@ final class MakeNameViewController: BaseViewController { view.endEditing(true) } + override func viewDidDisappear(_ animated: Bool) { + super.viewDidDisappear(animated) + coordinator.didFinishChildCoordinator() + } + override func configUI() { view.backgroundColor = .white setupBaseNavigationBar() diff --git a/30th-Assignment/30th-Assignment/30th-Assignment/Screens/Authentication/MakePasswordViewController.swift b/30th-Assignment/30th-Assignment/30th-Assignment/Screens/Authentication/MakePasswordViewController.swift index bb0b2c2..337d3c6 100644 --- a/30th-Assignment/30th-Assignment/30th-Assignment/Screens/Authentication/MakePasswordViewController.swift +++ b/30th-Assignment/30th-Assignment/30th-Assignment/Screens/Authentication/MakePasswordViewController.swift @@ -13,6 +13,7 @@ import Then final class MakePasswordViewController: BaseViewController { var userName: String? + private var coordinator: MakePasswordCoordinator private let titleLabel = UILabel().then { $0.text = "비밀번호 만들기" @@ -39,19 +40,29 @@ final class MakePasswordViewController: BaseViewController { $0.isEnabled = false let completeViewAction = UIAction { _ in - let completeVC = CompleteLoginViewController() - - completeVC.modalPresentationStyle = .fullScreen - completeVC.userName = self.userName - self.present(completeVC, animated: true) + self.coordinator.transitionToComplete(userName: self.userName ?? "", password: self.passwordTextField.text ?? "") } $0.addAction(completeViewAction, for: .touchUpInside) } + init(coordinator: MakePasswordCoordinator) { + self.coordinator = coordinator + super.init() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + override func viewDidLoad() { super.viewDidLoad() } + override func viewDidDisappear(_ animated: Bool) { + super.viewDidDisappear(animated) + coordinator.didFinishChildCoordinator() + } + override func touchesBegan(_ touches: Set, with event: UIEvent?) { view.endEditing(true) } diff --git a/30th-Assignment/30th-Assignment/30th-Assignment/Screens/Coordinator/AppCoordinator.swift b/30th-Assignment/30th-Assignment/30th-Assignment/Screens/Coordinator/AppCoordinator.swift new file mode 100644 index 0000000..279af3f --- /dev/null +++ b/30th-Assignment/30th-Assignment/30th-Assignment/Screens/Coordinator/AppCoordinator.swift @@ -0,0 +1,51 @@ +// +// AppCoordinator.swift +// 30th-Assignment +// +// Created by 김수연 on 2022/05/19. +// + +import Foundation +import UIKit + +final class AppCoordinator: Coordinator { + var presenter: UINavigationController + var childCoordinators: [Coordinator] = [] + weak var parentCoordinator: Coordinator? + var window: UIWindow + + // Appcoordinator 초기화 과정 + init(window: UIWindow) { + self.window = window + presenter = UINavigationController() + window.backgroundColor = .white + window.makeKeyAndVisible() + } + + func start() { + /// 자식 코디네이터 초기화 해주고, root를 탭바 컨트롤러로 ! + // removeChildCoordinators() + transitionToLogin() + } + + func transitionToTabbarController() -> UITabBarController { + // 탭바컨트롤러로 전환하면서 생성해주기 !! + let tabBarController = UITabBarController() + + let feedCoordinator = FeedCoordinator(presenter: UINavigationController()) + feedCoordinator.start() + + tabBarController.viewControllers = [ + feedCoordinator.presenter + ] + + return tabBarController + } + + func transitionToLogin() { + let childCoordinator = LoginCoordinator(presenter: presenter) + childCoordinator.parentCoordinator = self + addChildCoordinator(childCoordinator) + childCoordinator.start() + } +} diff --git a/30th-Assignment/30th-Assignment/30th-Assignment/Screens/Coordinator/AuthCoordinators/CompleteLoginCoordinator.swift b/30th-Assignment/30th-Assignment/30th-Assignment/Screens/Coordinator/AuthCoordinators/CompleteLoginCoordinator.swift new file mode 100644 index 0000000..ac754a1 --- /dev/null +++ b/30th-Assignment/30th-Assignment/30th-Assignment/Screens/Coordinator/AuthCoordinators/CompleteLoginCoordinator.swift @@ -0,0 +1,33 @@ +// +// CompleteLoginCoordinator.swift +// 30th-Assignment +// +// Created by 김수연 on 2022/05/19. +// + +import Foundation +import UIKit + +final class CompleteLoginCoordinator: Coordinator { + + var presenter: UINavigationController + var childCoordinators: [Coordinator] = [] + weak var parentCoordinator: Coordinator? + + init(presenter: UINavigationController) { + self.presenter = presenter + } + + func start(userName: String, password: String) { + let completeLoginViewController = CompleteLoginViewController(coordinator: self) + completeLoginViewController.userName = userName + completeLoginViewController.password = password + completeLoginViewController.modalPresentationStyle = .fullScreen + transition(to: self, with: completeLoginViewController, using: .modal, animated: true) + } + + func transitionToLogin() { + let loginCoordinator = LoginCoordinator(presenter: presenter) + loginCoordinator.start() + } +} diff --git a/30th-Assignment/30th-Assignment/30th-Assignment/Screens/Coordinator/AuthCoordinators/LoginCoordinator.swift b/30th-Assignment/30th-Assignment/30th-Assignment/Screens/Coordinator/AuthCoordinators/LoginCoordinator.swift new file mode 100644 index 0000000..5a946d2 --- /dev/null +++ b/30th-Assignment/30th-Assignment/30th-Assignment/Screens/Coordinator/AuthCoordinators/LoginCoordinator.swift @@ -0,0 +1,40 @@ +// +// LoginCoordinator.swift +// 30th-Assignment +// +// Created by 김수연 on 2022/05/19. +// + +import Foundation +import UIKit + +final class LoginCoordinator: Coordinator { + + var presenter: UINavigationController + var childCoordinators: [Coordinator] = [] + weak var parentCoordinator: Coordinator? + + init(presenter: UINavigationController) { + self.presenter = presenter + } + + func start() { + let loginViewController = LoginViewController(coordinator: self) + transition(to: self, with: loginViewController, using: .root) + // presenter.setViewControllers([loginViewController], animated: false) + } + + func transitionToMakeName() { + let childCoordinator = MakeNameCoordinator(presenter: presenter) + childCoordinator.parentCoordinator = self + addChildCoordinator(childCoordinator) + childCoordinator.start() + } + + func transitionToFeed() { + let feedCoordinator = FeedCoordinator(presenter: presenter) + feedCoordinator.start() + // transition(to: feedCoordinator, with: FeedViewController(coordinator: feedCoordinator), using: .root) + } +} + diff --git a/30th-Assignment/30th-Assignment/30th-Assignment/Screens/Coordinator/AuthCoordinators/MakeNameCoordinator.swift b/30th-Assignment/30th-Assignment/30th-Assignment/Screens/Coordinator/AuthCoordinators/MakeNameCoordinator.swift new file mode 100644 index 0000000..9fb3f40 --- /dev/null +++ b/30th-Assignment/30th-Assignment/30th-Assignment/Screens/Coordinator/AuthCoordinators/MakeNameCoordinator.swift @@ -0,0 +1,32 @@ +// +// SignUpCoordinator.swift +// 30th-Assignment +// +// Created by 김수연 on 2022/05/19. +// + +import Foundation +import UIKit + +final class MakeNameCoordinator: Coordinator { + + var presenter: UINavigationController + var childCoordinators: [Coordinator] = [] + weak var parentCoordinator: Coordinator? + + init(presenter: UINavigationController) { + self.presenter = presenter + } + + func start() { + let makeNameViewController = MakeNameViewController(coordinator: self) + presenter.setViewControllers([makeNameViewController], animated: true) + } + + func transitionToMakePassword(userName: String) { + let childCoordinator = MakePasswordCoordinator(presenter: presenter) + childCoordinator.parentCoordinator = self + addChildCoordinator(childCoordinator) + childCoordinator.start(userName: userName) + } +} diff --git a/30th-Assignment/30th-Assignment/30th-Assignment/Screens/Coordinator/AuthCoordinators/MakePasswordCoordinator.swift b/30th-Assignment/30th-Assignment/30th-Assignment/Screens/Coordinator/AuthCoordinators/MakePasswordCoordinator.swift new file mode 100644 index 0000000..b2e611a --- /dev/null +++ b/30th-Assignment/30th-Assignment/30th-Assignment/Screens/Coordinator/AuthCoordinators/MakePasswordCoordinator.swift @@ -0,0 +1,33 @@ +// +// MakePasswordCoordinator.swift +// 30th-Assignment +// +// Created by 김수연 on 2022/05/19. +// + +import Foundation +import UIKit + +final class MakePasswordCoordinator: Coordinator { + + var presenter: UINavigationController + var childCoordinators: [Coordinator] = [] + weak var parentCoordinator: Coordinator? + + init(presenter: UINavigationController) { + self.presenter = presenter + } + + func start(userName: String) { + let makePasswordViewController = MakePasswordViewController(coordinator: self) + makePasswordViewController.userName = userName + transition(to: self, with: makePasswordViewController, using: .push, animated: true) + } + + func transitionToComplete(userName: String, password: String) { + let childCoordinator = CompleteLoginCoordinator(presenter: presenter) + childCoordinator.parentCoordinator = self + addChildCoordinator(childCoordinator) + childCoordinator.start(userName: userName, password: password) + } +} diff --git a/30th-Assignment/30th-Assignment/30th-Assignment/Screens/Coordinator/Foundation/Coordinator.swift b/30th-Assignment/30th-Assignment/30th-Assignment/Screens/Coordinator/Foundation/Coordinator.swift new file mode 100644 index 0000000..85855ea --- /dev/null +++ b/30th-Assignment/30th-Assignment/30th-Assignment/Screens/Coordinator/Foundation/Coordinator.swift @@ -0,0 +1,86 @@ +// +// Coordinator.swift +// 30th-Assignment +// +// Created by 김수연 on 2022/05/19. +// + +import Foundation +import UIKit + +protocol Coordinator: AnyObject { + // 코디네이터가 화면 전환을 위해 가지고 있는 네비게이션 컨트롤러 ! + var presenter: UINavigationController { get set } + // 자식 코디네이터들을 담을 배열 + var childCoordinators: [Coordinator] { get set } + // 자식 코디네이터들을 생성하고 제거하는 부모코디네이터 + var parentCoordinator: Coordinator? { get set } +} + +extension Coordinator { + + // 화면 전환스타일에 따라 구현해놓은 메서드 + func transition(to coordinator: Coordinator, + with viewController: UIViewController, + using style: TransitionStyle, + animated: Bool = false, + completion: (() -> Void)? = nil) { + + switch style { + case .root: + // root를 재설정해주고 이동하는 transition + presenter = UINavigationController(rootViewController: viewController) + let scene = UIApplication.shared.connectedScenes.first + if let sceneDelegate: SceneDelegate = scene?.delegate as? SceneDelegate { + sceneDelegate.window?.rootViewController = presenter + } + // 음 어떤 코디네이터로 root설정을 하므로 돌아가므로 원래 자식 코디네이터들 전부 삭제 ~ + removeChildCoordinators() + // 돌아간 코디네이터를 자식 코디네이터에 추가 - 이건 잘 모르겠음 + addChildCoordinator(coordinator) + case .push: + // push해서 이동할 코디네이터를 자식코디네이터로 추가 + addChildCoordinator(coordinator) + // navigation push + presenter.pushViewController(viewController, animated: animated) + case .modal: + // present할 코디네이터를 자식 코디네이터로 추가 + addChildCoordinator(coordinator) + // present ! + presenter.present(viewController, animated: animated, completion: completion) + } + } + + /// 자식 코디네이터가 할일을 끝냈을 때 호출 + /// : 자신의 부모코디네이터에 접근해서 자기 자신을 자식코디네이터 배열에서 지워줌 + /// 보통 viewDidDisappear 에서 호출 ! 화면이 사라질 시점 ! + /// or NavigationController Delegate의 didShow() 메서드에서 호출 + func didFinishChildCoordinator() { + parentCoordinator?.removeChildCoordinator(self) + } + + // 자식 코디네이터를 추가하는 메서드 : 부모에서 이동할 코디네이터를 자식으로 추가해서 관리 ! + func addChildCoordinator(_ childCoordinator: Coordinator) { + self.childCoordinators.append(childCoordinator) + } + + /// 자식 코디네이터를 지우는 메서드 + /// 예를 들어 어떤 VC에서 첫화면으로 돌아간다면 ? parent coordinator한테 알린다음에 + /// childcoordinator 배열에서 VC들을 지워야합니다 + /// 위에 보면 didFinsh ~ 메서드 내부에서 호출해주는데 , 부모 코디네이터에 접근해서 자식을 지워줍니다 ~~ + func removeChildCoordinator(_ childCoordinator: Coordinator) { + /// === 는 클래스의 두 인스턴스가 동일한 메모리를 가리키는지 점검하는 연산자 이므로 + /// 코디네이터 프로토콜을 클래스 전용 프로토콜로 만들자 !! + /// 내가 이해한 바로는 받은 파라미터로 받은 childCoordinator랑 동일 메모리를 가지는 그 코디네이터를 찾아서 + /// 인덱스를 반환해주는것 같다 ! + if let index = childCoordinators.firstIndex(where: { $0 === childCoordinator }) { + childCoordinators.remove(at: index) + } + } + + func removeChildCoordinators() { + /// 따로 파라미터를 받는게 아니라 전부 삭제 + childCoordinators.forEach { $0.removeChildCoordinators() } + childCoordinators.removeAll() + } +} diff --git a/30th-Assignment/30th-Assignment/30th-Assignment/Screens/Coordinator/Foundation/TransitionModel.swift b/30th-Assignment/30th-Assignment/30th-Assignment/Screens/Coordinator/Foundation/TransitionModel.swift new file mode 100644 index 0000000..68ac181 --- /dev/null +++ b/30th-Assignment/30th-Assignment/30th-Assignment/Screens/Coordinator/Foundation/TransitionModel.swift @@ -0,0 +1,22 @@ +// +// TransitionModel.swift +// 30th-Assignment +// +// Created by 김수연 on 2022/05/19. +// + +import Foundation + +/// 화면전환 스타일 enum + +enum TransitionStyle { + case root + case push + case modal +} + +enum TransitionError { + case navigationControllerMissing + case cannotPop + case unknown +} diff --git a/30th-Assignment/30th-Assignment/30th-Assignment/Screens/Coordinator/TabBarCoordinators/FeedCoordinator.swift b/30th-Assignment/30th-Assignment/30th-Assignment/Screens/Coordinator/TabBarCoordinators/FeedCoordinator.swift new file mode 100644 index 0000000..1ee2440 --- /dev/null +++ b/30th-Assignment/30th-Assignment/30th-Assignment/Screens/Coordinator/TabBarCoordinators/FeedCoordinator.swift @@ -0,0 +1,26 @@ +// +// FeedCoordinator.swift +// 30th-Assignment +// +// Created by 김수연 on 2022/05/19. +// + +import Foundation +import UIKit + +final class FeedCoordinator: Coordinator { + + var presenter: UINavigationController + var childCoordinators: [Coordinator] = [] + weak var parentCoordinator: Coordinator? + + init(presenter: UINavigationController) { + self.presenter = presenter + } + + func start() { + let feedViewController = FeedViewController(coordinator: self) + feedViewController.tabBarItem = UITabBarItem(title: "", image: ImageLiteral.iconHome,selectedImage: ImageLiteral.iconHomeSelected) + presenter.setViewControllers([feedViewController], animated: false) + } +} diff --git a/30th-Assignment/30th-Assignment/30th-Assignment/Screens/Feed/FeedViewController.swift b/30th-Assignment/30th-Assignment/30th-Assignment/Screens/Feed/FeedViewController.swift index e94d8c9..0434172 100644 --- a/30th-Assignment/30th-Assignment/30th-Assignment/Screens/Feed/FeedViewController.swift +++ b/30th-Assignment/30th-Assignment/30th-Assignment/Screens/Feed/FeedViewController.swift @@ -9,10 +9,26 @@ import UIKit class FeedViewController: BaseViewController { + private var coordinator: FeedCoordinator + + init(coordinator: FeedCoordinator) { + self.coordinator = coordinator + super.init() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + override func viewDidLoad() { super.viewDidLoad() } + override func viewDidDisappear(_ animated: Bool) { + super.viewDidDisappear(animated) + coordinator.didFinishChildCoordinator() + } + override func configUI() { view.backgroundColor = .blue } diff --git a/30th-Assignment/30th-Assignment/30th-Assignment/Screens/Tabbar/TabBarController.swift b/30th-Assignment/30th-Assignment/30th-Assignment/Screens/Tabbar/TabBarController.swift index 603d457..cef026b 100644 --- a/30th-Assignment/30th-Assignment/30th-Assignment/Screens/Tabbar/TabBarController.swift +++ b/30th-Assignment/30th-Assignment/30th-Assignment/Screens/Tabbar/TabBarController.swift @@ -27,12 +27,12 @@ final class TabBarController: UITabBarController { } private func makeTabBarItems() { - let feedVC = FeedViewController() - feedVC.tabBarItem = UITabBarItem( - title: "", - image: ImageLiteral.iconHome, - selectedImage: ImageLiteral.iconHomeSelected - ) +// let feedVC = FeedViewController(coordinator: FeedCoordinator) +// feedVC.tabBarItem = UITabBarItem( +// title: "", +// image: ImageLiteral.iconHome, +// selectedImage: ImageLiteral.iconHomeSelected +// ) let searchVC = SearchViewController() searchVC.tabBarItem = UITabBarItem( @@ -64,7 +64,7 @@ final class TabBarController: UITabBarController { ) let tabBarViewControllers: [UIViewController] = { - let tabBarViewControllers = [feedVC, + let tabBarViewControllers = [ searchVC, reelsVC, shopVC,