diff --git a/Runnect-iOS/Runnect-iOS.xcodeproj/project.pbxproj b/Runnect-iOS/Runnect-iOS.xcodeproj/project.pbxproj index 24c5a15b..27391295 100644 --- a/Runnect-iOS/Runnect-iOS.xcodeproj/project.pbxproj +++ b/Runnect-iOS/Runnect-iOS.xcodeproj/project.pbxproj @@ -37,6 +37,7 @@ CE17F0382961BF8B00E1DED0 /* FontLiterals.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE17F0372961BF8B00E1DED0 /* FontLiterals.swift */; }; CE29D582296402B500F47542 /* CourseDrawingVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE29D581296402B500F47542 /* CourseDrawingVC.swift */; }; CE29D584296416D800F47542 /* caculateStatusBarHeight.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE29D583296416D800F47542 /* caculateStatusBarHeight.swift */; }; + CE3A53C5296C6017003D518C /* KeychainManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE3A53C4296C6017003D518C /* KeychainManager.swift */; }; CE40BB1C2967E4910030ABCA /* RunningWaitingVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE40BB1B2967E4910030ABCA /* RunningWaitingVC.swift */; }; CE40BB1E2968054F0030ABCA /* BaseResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE40BB1D2968054F0030ABCA /* BaseResponse.swift */; }; CE40BB20296805F70030ABCA /* NetworkResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE40BB1F296805F70030ABCA /* NetworkResult.swift */; }; @@ -107,6 +108,7 @@ CEC2A68E2962AF2C00160BF7 /* RNMarker.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEC2A68D2962AF2C00160BF7 /* RNMarker.swift */; }; CEC2A6902962B06C00160BF7 /* convertLocationObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEC2A68F2962B06C00160BF7 /* convertLocationObject.swift */; }; CEC2A6922962BE2900160BF7 /* DepartureSearchVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEC2A6912962BE2900160BF7 /* DepartureSearchVC.swift */; }; + CECBAD2F296C2F3C00AC8976 /* SignInRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = CECBAD2E296C2F3C00AC8976 /* SignInRouter.swift */; }; CEEC6B3A2961C4F300D00E1E /* CourseDrawingHomeVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEEC6B392961C4F300D00E1E /* CourseDrawingHomeVC.swift */; }; CEEC6B3C2961C51A00D00E1E /* CourseStorageVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEEC6B3B2961C51A00D00E1E /* CourseStorageVC.swift */; }; CEEC6B3E2961C53700D00E1E /* CourseDiscoveryVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEEC6B3D2961C53700D00E1E /* CourseDiscoveryVC.swift */; }; @@ -172,6 +174,7 @@ CE17F0372961BF8B00E1DED0 /* FontLiterals.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FontLiterals.swift; sourceTree = ""; }; CE29D581296402B500F47542 /* CourseDrawingVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CourseDrawingVC.swift; sourceTree = ""; }; CE29D583296416D800F47542 /* caculateStatusBarHeight.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = caculateStatusBarHeight.swift; sourceTree = ""; }; + CE3A53C4296C6017003D518C /* KeychainManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainManager.swift; sourceTree = ""; }; CE40BB1B2967E4910030ABCA /* RunningWaitingVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunningWaitingVC.swift; sourceTree = ""; }; CE40BB1D2968054F0030ABCA /* BaseResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseResponse.swift; sourceTree = ""; }; CE40BB1F296805F70030ABCA /* NetworkResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkResult.swift; sourceTree = ""; }; @@ -247,6 +250,7 @@ CEC2A68D2962AF2C00160BF7 /* RNMarker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RNMarker.swift; sourceTree = ""; }; CEC2A68F2962B06C00160BF7 /* convertLocationObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = convertLocationObject.swift; sourceTree = ""; }; CEC2A6912962BE2900160BF7 /* DepartureSearchVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DepartureSearchVC.swift; sourceTree = ""; }; + CECBAD2E296C2F3C00AC8976 /* SignInRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignInRouter.swift; sourceTree = ""; }; CEEC6B392961C4F300D00E1E /* CourseDrawingHomeVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CourseDrawingHomeVC.swift; sourceTree = ""; }; CEEC6B3B2961C51A00D00E1E /* CourseStorageVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CourseStorageVC.swift; sourceTree = ""; }; CEEC6B3D2961C53700D00E1E /* CourseDiscoveryVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CourseDiscoveryVC.swift; sourceTree = ""; }; @@ -660,6 +664,7 @@ isa = PBXGroup; children = ( CE10064029680C9F00FD31FB /* .gitkeep */, + CECBAD2E296C2F3C00AC8976 /* SignInRouter.swift */, ); path = SignInRouter; sourceTree = ""; @@ -880,6 +885,7 @@ CE29D583296416D800F47542 /* caculateStatusBarHeight.swift */, CE14677729658C7200DCEA1B /* Stopwatch.swift */, CE9291282965E01D0010959C /* RNTimeFormatter.swift */, + CE3A53C4296C6017003D518C /* KeychainManager.swift */, ); path = Utils; sourceTree = ""; @@ -1264,6 +1270,7 @@ DA20D84E2966A9B300F1581F /* SearchVC.swift in Sources */, CE1006572968230800FD31FB /* DepartureLocationModel.swift in Sources */, CE6655EC295D88D000C64E12 /* UITableView+.swift in Sources */, + CECBAD2F296C2F3C00AC8976 /* SignInRouter.swift in Sources */, CEEC6B3A2961C4F300D00E1E /* CourseDrawingHomeVC.swift in Sources */, CEC2A6902962B06C00160BF7 /* convertLocationObject.swift in Sources */, CEC2A6852961F92C00160BF7 /* CustomButton.swift in Sources */, @@ -1286,6 +1293,7 @@ CE5875A4296015D2005D967E /* Encodable+.swift in Sources */, A3BC2F4129667A0D00198261 /* NicknameEditorVC.swift in Sources */, CE0C23742966D62A00B45063 /* PagedView.swift in Sources */, + CE3A53C5296C6017003D518C /* KeychainManager.swift in Sources */, CE14677A2965A80700DCEA1B /* CustomBottomSheetVC.swift in Sources */, CEEC6B4B2961D89700D00E1E /* CustomNavigationBar.swift in Sources */, CE40BB2D296808B00030ABCA /* DepartureSearchingRouter.swift in Sources */, diff --git a/Runnect-iOS/Runnect-iOS/Global/Supports/SceneDelegate.swift b/Runnect-iOS/Runnect-iOS/Global/Supports/SceneDelegate.swift index b1a5c3a4..f2baba80 100644 --- a/Runnect-iOS/Runnect-iOS/Global/Supports/SceneDelegate.swift +++ b/Runnect-iOS/Runnect-iOS/Global/Supports/SceneDelegate.swift @@ -16,7 +16,8 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { guard let windowScene = (scene as? UIWindowScene) else { return } let window = UIWindow(windowScene: windowScene) - window.rootViewController = TabBarController() + let nav = UINavigationController(rootViewController: SplashVC()) + window.rootViewController = nav self.window = window window.makeKeyAndVisible() } diff --git a/Runnect-iOS/Runnect-iOS/Global/Utils/KeychainManager.swift b/Runnect-iOS/Runnect-iOS/Global/Utils/KeychainManager.swift new file mode 100644 index 00000000..4fd75aa4 --- /dev/null +++ b/Runnect-iOS/Runnect-iOS/Global/Utils/KeychainManager.swift @@ -0,0 +1,105 @@ +// +// KeychainManager.swift +// Runnect-iOS +// +// Created by sejin on 2023/01/09. +// + +import UIKit + +struct KeychainManager { + + static let shared = KeychainManager() + + private let service = Bundle.main.bundleIdentifier + private let deviceId = "deviceId" + + private init() {} + + func storeDeviceId() -> Bool { + guard let service = service else { + print("Keychain >> addItem Fail with no service") + return false + } + + let query: [CFString: Any] = [kSecClass: kSecClassGenericPassword, + kSecAttrService: service, + kSecAttrAccount: deviceId, + kSecValueData: setDeviceID().data(using: .utf8, allowLossyConversion: false)!] + + // keychain 에 저장을 수행한 결과 값 반환 (true / false) + let status: OSStatus = SecItemAdd(query as CFDictionary, nil) + if status == errSecSuccess { + print("") + print("Keychain >> addItem() : Success Status : \(status)]") + print("") + return true + } else { + print("") + print("[Keychain >> addItem() : Fail Status : \(status)]") + print("") + return false + } + } + + func getDeviceId() -> String { + guard let service = service else { + print("Keychain >> getDeviceId Fail with no service") + return "" + } + + let query: [CFString: Any] = [kSecClass: kSecClassGenericPassword, // 보안 데이터 저장 + kSecAttrService: service, + kSecAttrAccount: deviceId, + kSecReturnData: true, + kSecReturnAttributes: true, + kSecMatchLimit: kSecMatchLimitOne] + + var dataTypeRef: CFTypeRef? + let status: OSStatus = SecItemCopyMatching(query as CFDictionary, &dataTypeRef) + + if status == errSecSuccess { + guard let existingItem = dataTypeRef as? [String: Any] + else { + print("Keychain >> getDeviceID() : Type Check : false") + return "" + } + let deviceData = existingItem[kSecValueData as String] as? Data + let uuidData = String(data: deviceData!, encoding: .utf8)! + + print("Keychain >> getDeviceID() : Success Status : \(status)") + return uuidData + } else if status == errSecItemNotFound || status == -25300 { + print("Keychain >> getDeviceID() : Fail Status : 저장된 데이터가 없습니다") + return "" + } else { + print("Keychain >> getDeviceID() : Fail Status : \(status)") + return "" + } + } + + func deleteDeviceID() -> Bool { + guard let service = self.service + else { + print("Keychain >> deleteDeviceID() : Service Check : false") + return false + } + + let query: [CFString: Any] = [kSecClass: kSecClassGenericPassword, + kSecAttrService: service, + kSecAttrAccount: deviceId] + + let status: OSStatus = SecItemDelete(query as CFDictionary) + if status == errSecSuccess { + print("Keychain >> deleteDeviceID() : Success Status : \(status)") + return true + } else { + print("Keychain >> deleteDeviceID() : Fail Status : \(status)") + return false + } + } + + func setDeviceID() -> String { + return UIDevice.current.identifierForVendor!.uuidString + } +} diff --git a/Runnect-iOS/Runnect-iOS/Global/Utils/UserDefaultKeyList.swift b/Runnect-iOS/Runnect-iOS/Global/Utils/UserDefaultKeyList.swift index c7ed5ade..90a289e2 100644 --- a/Runnect-iOS/Runnect-iOS/Global/Utils/UserDefaultKeyList.swift +++ b/Runnect-iOS/Runnect-iOS/Global/Utils/UserDefaultKeyList.swift @@ -9,6 +9,6 @@ import Foundation struct UserDefaultKeyList { struct Auth { - @UserDefaultWrapper(key: "deviceId") public static var deviceId + @UserDefaultWrapper(key: "didSignIn") public static var didSignIn } } diff --git a/Runnect-iOS/Runnect-iOS/Network/Router/SignInRouter/SignInRouter.swift b/Runnect-iOS/Runnect-iOS/Network/Router/SignInRouter/SignInRouter.swift new file mode 100644 index 00000000..49343eaa --- /dev/null +++ b/Runnect-iOS/Runnect-iOS/Network/Router/SignInRouter/SignInRouter.swift @@ -0,0 +1,52 @@ +// +// SignInRouter.swift +// Runnect-iOS +// +// Created by sejin on 2023/01/09. +// + +import Foundation + +import Moya + +enum SignInRouter { + case signUp(nickname: String) +} + +extension SignInRouter: TargetType { + var baseURL: URL { + guard let url = URL(string: Config.baseURL) else { + fatalError("baseURL could not be configured") + } + + return url + } + + var path: String { + switch self { + case .signUp: + return "/user" + } + } + + var method: Moya.Method { + switch self { + case .signUp: + return .post + } + } + + var task: Moya.Task { + switch self { + case .signUp(let nickname): + return .requestParameters(parameters: ["nickname": nickname], encoding: JSONEncoding.default) + } + } + + var headers: [String: String]? { + switch self { + case .signUp: + return Config.headerWithDeviceId + } + } +} diff --git a/Runnect-iOS/Runnect-iOS/Presentation/SignIn/VC/SignInVC.swift b/Runnect-iOS/Runnect-iOS/Presentation/SignIn/VC/SignInVC.swift index 0c0fd5f0..0f4dffcf 100644 --- a/Runnect-iOS/Runnect-iOS/Presentation/SignIn/VC/SignInVC.swift +++ b/Runnect-iOS/Runnect-iOS/Presentation/SignIn/VC/SignInVC.swift @@ -7,10 +7,16 @@ import UIKit +import Moya + final class SignInVC: UIViewController { // MARK: - Properties + private var signInProvider = MoyaProvider( + plugins: [NetworkLoggerPlugin(verbose: true)] + ) + private let nicknameMaxLength = 7 // MARK: - UI Components @@ -50,6 +56,7 @@ final class SignInVC: UIViewController { self.setUI() self.setLayout() self.setDelegate() + self.setAddTarget() } } @@ -60,9 +67,19 @@ extension SignInVC { self.nicknameTextField.delegate = self } + private func setAddTarget() { + self.startButton.addTarget(self, action: #selector(startButtonDidTap), for: .touchUpInside) + } + private func changeTextFieldLayerColor(_ isEditing: Bool) { nicknameTextField.layer.borderColor = isEditing ? UIColor.m1.cgColor : UIColor.g3.cgColor } + + private func pushToTabBarController() { + let tabBarController = TabBarController() + guard let window = self.view.window else { return } + ViewControllerUtils.setRootViewController(window: window, viewController: tabBarController, withAnimation: true) + } } // MARK: - @objc Function @@ -80,6 +97,11 @@ extension SignInVC { self.nicknameTextField.text = String(newString) } } + + @objc func startButtonDidTap() { + guard let nickname = nicknameTextField.text else { return } + self.signIn(nickname: nickname) + } } // MARK: - UI & Layout @@ -124,3 +146,37 @@ extension SignInVC: UITextFieldDelegate { return true } } + +// MARK: - Network + +extension SignInVC { + func signIn(nickname: String) { + LoadingIndicator.showLoading() + signInProvider.request(.signUp(nickname: nickname)) { [weak self] response in + LoadingIndicator.hideLoading() + guard let self = self else { return } + switch response { + case .success(let result): + let status = result.statusCode + if 200..<300 ~= status { + do { + let responseDto = try result.map(BaseResponse.self) + if responseDto.status == 200 { + self.pushToTabBarController() + } else { + self.showToast(message: responseDto.message) + } + } catch { + print(error.localizedDescription) + } + } + if status >= 400 { + print("400 error") + } + case .failure(let error): + print(error.localizedDescription) + self.showToast(message: "네트워크 통신 실패") + } + } + } +} diff --git a/Runnect-iOS/Runnect-iOS/Presentation/Splash/VC/SplashVC.swift b/Runnect-iOS/Runnect-iOS/Presentation/Splash/VC/SplashVC.swift index e98345e0..59385253 100644 --- a/Runnect-iOS/Runnect-iOS/Presentation/Splash/VC/SplashVC.swift +++ b/Runnect-iOS/Runnect-iOS/Presentation/Splash/VC/SplashVC.swift @@ -31,19 +31,38 @@ final class SplashVC: UIViewController { self.setUI() self.setNavigationBar() self.setLayout() - self.pushToSignInView() + self.checkDidSignIn() } } // MARK: - Methods extension SplashVC { - private func pushToSignInView() { - DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { - let signInVC = SignInVC() - self.navigationController?.pushViewController(signInVC, animated: true) + private func checkDidSignIn() { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + let deviceId = KeychainManager.shared.getDeviceId() + + if deviceId.isEmpty { + let deviceIdStoreSuccess = KeychainManager.shared.storeDeviceId() + guard deviceIdStoreSuccess else { return } + self.pushToSignInView() + return + } + + self.pushToTabBarController() } } + + private func pushToSignInView() { + let signInVC = SignInVC() + self.navigationController?.pushViewController(signInVC, animated: true) + } + + private func pushToTabBarController() { + let tabBarController = TabBarController() + guard let window = self.view.window else { return } + ViewControllerUtils.setRootViewController(window: window, viewController: tabBarController, withAnimation: true) + } } // MARK: - UI & Layout