diff --git a/Week4/AppDelegate.swift b/Week4/AppDelegate.swift new file mode 100644 index 0000000..290d2e4 --- /dev/null +++ b/Week4/AppDelegate.swift @@ -0,0 +1,36 @@ +// +// AppDelegate.swift +// Week4 +// +// Created by 오서영 on 2024/04/27. +// + +import UIKit + +@main +class AppDelegate: UIResponder, UIApplicationDelegate { + + + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + // Override point for customization after application launch. + return true + } + + // MARK: UISceneSession Lifecycle + + func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { + // Called when a new scene session is being created. + // Use this method to select a configuration to create the new scene with. + return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) + } + + func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { + // Called when the user discards a scene session. + // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. + // Use this method to release any resources that were specific to the discarded scenes, as they will not return. + } + + +} + diff --git a/Week4/Assets.xcassets/AccentColor.colorset/Contents.json b/Week4/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 0000000..eb87897 --- /dev/null +++ b/Week4/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Week4/Assets.xcassets/AppIcon.appiconset/Contents.json b/Week4/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..13613e3 --- /dev/null +++ b/Week4/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,13 @@ +{ + "images" : [ + { + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Week4/Assets.xcassets/Contents.json b/Week4/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Week4/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Week4/Base.lproj/LaunchScreen.storyboard b/Week4/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..865e932 --- /dev/null +++ b/Week4/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Week4/Base.lproj/Main.storyboard b/Week4/Base.lproj/Main.storyboard new file mode 100644 index 0000000..25a7638 --- /dev/null +++ b/Week4/Base.lproj/Main.storyboard @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Week4/Config.swift b/Week4/Config.swift new file mode 100644 index 0000000..d0f78b6 --- /dev/null +++ b/Week4/Config.swift @@ -0,0 +1,34 @@ +// +// Config.swift +// Week4 +// +// 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/Week4/Info.plist b/Week4/Info.plist new file mode 100644 index 0000000..27730bb --- /dev/null +++ b/Week4/Info.plist @@ -0,0 +1,34 @@ + + + + + BASE_URL + $(BASE_URL) + UISupportedInterfaceOrientations~ipad + + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneConfigurationName + Default Configuration + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + UISceneStoryboardFile + Main + + + + + + diff --git a/Week4/MoyaLoggingPlugin.swift b/Week4/MoyaLoggingPlugin.swift new file mode 100644 index 0000000..9dad7b3 --- /dev/null +++ b/Week4/MoyaLoggingPlugin.swift @@ -0,0 +1,66 @@ +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/Week4/NetworkResult.swift b/Week4/NetworkResult.swift new file mode 100644 index 0000000..4f3a36d --- /dev/null +++ b/Week4/NetworkResult.swift @@ -0,0 +1,10 @@ +import Foundation + +enum NetworkResult { + case success(T) // 서버 통신 성공했을 때, + case requestErr // 요청 에러 발생했을 때, + case decodedErr // 디코딩 오류 발생했을 때 + case pathErr // 경로 에러 발생했을 때, + case serverErr // 서버의 내부적 에러가 발생했을 때, + case networkFail // 네트워크 연결 실패했을 때 +} diff --git a/Week4/SceneDelegate.swift b/Week4/SceneDelegate.swift new file mode 100644 index 0000000..0c5bfab --- /dev/null +++ b/Week4/SceneDelegate.swift @@ -0,0 +1,52 @@ +// +// SceneDelegate.swift +// Week4 +// +// Created by 오서영 on 2024/04/27. +// + +import UIKit + +class SceneDelegate: UIResponder, UIWindowSceneDelegate { + + var window: UIWindow? + + + func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { + // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. + // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. + // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). + guard let _ = (scene as? UIWindowScene) else { return } + } + + func sceneDidDisconnect(_ scene: UIScene) { + // Called as the scene is being released by the system. + // This occurs shortly after the scene enters the background, or when its session is discarded. + // Release any resources associated with this scene that can be re-created the next time the scene connects. + // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead). + } + + func sceneDidBecomeActive(_ scene: UIScene) { + // Called when the scene has moved from an inactive state to an active state. + // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. + } + + func sceneWillResignActive(_ scene: UIScene) { + // Called when the scene will move from an active state to an inactive state. + // This may occur due to temporary interruptions (ex. an incoming phone call). + } + + func sceneWillEnterForeground(_ scene: UIScene) { + // Called as the scene transitions from the background to the foreground. + // Use this method to undo the changes made on entering the background. + } + + func sceneDidEnterBackground(_ scene: UIScene) { + // Called as the scene transitions from the foreground to the background. + // Use this method to save data, release shared resources, and store enough scene-specific state information + // to restore the scene back to its current state. + } + + +} + diff --git a/Week4/UserAuthen/SignUpRequestModel.swift b/Week4/UserAuthen/SignUpRequestModel.swift new file mode 100644 index 0000000..7e87828 --- /dev/null +++ b/Week4/UserAuthen/SignUpRequestModel.swift @@ -0,0 +1,8 @@ +import Foundation + +struct SignUpRequestModel: Codable { + let authenticationId: String + let password: String + let nickname: String + let phone: String +} diff --git a/Week4/UserAuthen/SignUpResponseModel.swift b/Week4/UserAuthen/SignUpResponseModel.swift new file mode 100644 index 0000000..65cd7a8 --- /dev/null +++ b/Week4/UserAuthen/SignUpResponseModel.swift @@ -0,0 +1,6 @@ +import Foundation + +struct SignUpResponseModel: Codable { + let code: Int + let message: String +} diff --git a/Week4/UserAuthen/SignUpViewController.swift b/Week4/UserAuthen/SignUpViewController.swift new file mode 100644 index 0000000..ea5ec1c --- /dev/null +++ b/Week4/UserAuthen/SignUpViewController.swift @@ -0,0 +1,176 @@ +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 viewDidLoad() { + super.viewDidLoad() + + self.view.backgroundColor = .white + setTarget() + } + + private func setTarget() { + rootView.signUpButton.addTarget(self, action: #selector(signUpButtonDidTap), for: .touchUpInside) + } + + @objc private func signUpButtonDidTap() { +// 회원가입 버튼을 눌렀을 때 각 TextField의 값을 가져와서 서버에 요청할 데이터 객체를 만들어 줍니다 + 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/Week4/UserAuthen/UserService.swift b/Week4/UserAuthen/UserService.swift new file mode 100644 index 0000000..40514a3 --- /dev/null +++ b/Week4/UserAuthen/UserService.swift @@ -0,0 +1,74 @@ +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/Week4/UserAuthen/UserTargetType.swift b/Week4/UserAuthen/UserTargetType.swift new file mode 100644 index 0000000..e6918b0 --- /dev/null +++ b/Week4/UserAuthen/UserTargetType.swift @@ -0,0 +1,60 @@ +import Foundation + +import Moya + + +enum UserTargetType { + case getUserInfo(memberId: String) + case signUp(request: SignUpRequestModel) +} + +extension UserTargetType: TargetType { +// 말 그대로 Base URL을 적어주면 됩니다. Config. baseURL을 통해서 접근을 하면 됩니다 + var baseURL: URL { + return URL(string: Config.baseURL)! + } + +// 회원가입에 Path를 추가적으로 적어줍니다. 여기서는 member/join 이겠죠! + var path: String { + switch self { + case .signUp: + return "/member/join" + case .getUserInfo(memberId: let memberId): + return "/member/info" + } + } + +// http method를 적어주면 됩니다. 명세서에 따라 post를 입력해줍니다 + var method: Moya.Method { + switch self { + case .signUp: + return .post + case .getUserInfo: + return .get + } + } + +// 어떤 방식으로 통신할 것인지를 정해주야 합니다. 우리는 Encodable한 body를 요청시 보내줄 것이기 때문에 +// .requestJSONEncodable에 SignUpRequsetModel 타입의 객체를 인자로 넣어줍니다 + var task: Moya.Task { + switch self { + case .signUp(let request): + return .requestJSONEncodable(request) + case .getUserInfo: + return .requestPlain + } + } + +// Request Header에 들어갈 내용을 적어줍니다. 저희는 Content-type이 JSON이라는 정보를 보내봅시다! + var headers: [String : String]? { + switch self { + case.signUp: + return ["Content-Type": "application/json"] + case .getUserInfo(let memberId): + return ["Content-Type": "application/json", + "memberId" : memberId] + } + + } +} + diff --git a/Week4/UserInfo/CheckUserInfoController.swift b/Week4/UserInfo/CheckUserInfoController.swift new file mode 100644 index 0000000..683c419 --- /dev/null +++ b/Week4/UserInfo/CheckUserInfoController.swift @@ -0,0 +1,85 @@ +import Foundation +import UIKit + +final class CheckUserInfoViewController: UIViewController { + private let logoImageView = UIImageView(image: .logo2) + 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/Week4/UserInfo/UserInfoResponseModel.swift b/Week4/UserInfo/UserInfoResponseModel.swift new file mode 100644 index 0000000..51f5d1d --- /dev/null +++ b/Week4/UserInfo/UserInfoResponseModel.swift @@ -0,0 +1,13 @@ +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/Week4/ViewController.swift b/Week4/ViewController.swift new file mode 100644 index 0000000..ae9b637 --- /dev/null +++ b/Week4/ViewController.swift @@ -0,0 +1,19 @@ +// +// ViewController.swift +// Week4 +// +// Created by 오서영 on 2024/04/27. +// + +import UIKit + +class ViewController: UIViewController { + + override func viewDidLoad() { + super.viewDidLoad() + // Do any additional setup after loading the view. + } + + +} + diff --git a/Week7/Week7.xcodeproj/project.pbxproj b/Week7/Week7.xcodeproj/project.pbxproj new file mode 100644 index 0000000..3a6f946 --- /dev/null +++ b/Week7/Week7.xcodeproj/project.pbxproj @@ -0,0 +1,421 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 56; + objects = { + +/* Begin PBXBuildFile section */ + 2CD383042C01C7E00085B047 /* Week7App.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CD383032C01C7E00085B047 /* Week7App.swift */; }; + 2CD383062C01C7E00085B047 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CD383052C01C7E00085B047 /* ContentView.swift */; }; + 2CD383082C01C7E30085B047 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2CD383072C01C7E30085B047 /* Assets.xcassets */; }; + 2CD3830B2C01C7E30085B047 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2CD3830A2C01C7E30085B047 /* Preview Assets.xcassets */; }; + 2CD383132C01C7F80085B047 /* RxCocoa in Frameworks */ = {isa = PBXBuildFile; productRef = 2CD383122C01C7F80085B047 /* RxCocoa */; }; + 2CD383152C01C7F80085B047 /* RxSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 2CD383142C01C7F80085B047 /* RxSwift */; }; + 2CD383172C01C8300085B047 /* LoginViewController_DI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CD383162C01C8300085B047 /* LoginViewController_DI.swift */; }; + 2CD383192C01C83C0085B047 /* ViewModelType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CD383182C01C83C0085B047 /* ViewModelType.swift */; }; + 2CD3831B2C01C8470085B047 /* LoginViewModel_Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CD3831A2C01C8470085B047 /* LoginViewModel_Rx.swift */; }; + 2CD3831D2C01C8530085B047 /* LoginViewController_Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CD3831C2C01C8530085B047 /* LoginViewController_Rx.swift */; }; + 2CD3831F2C01C8630085B047 /* LoginViewController_LiveCodingTemplete.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CD3831E2C01C8630085B047 /* LoginViewController_LiveCodingTemplete.swift */; }; + 2CD383222C01C8BB0085B047 /* SnapKit in Frameworks */ = {isa = PBXBuildFile; productRef = 2CD383212C01C8BB0085B047 /* SnapKit */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 2CD383002C01C7E00085B047 /* Week7.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Week7.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 2CD383032C01C7E00085B047 /* Week7App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Week7App.swift; sourceTree = ""; }; + 2CD383052C01C7E00085B047 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; + 2CD383072C01C7E30085B047 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 2CD3830A2C01C7E30085B047 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; + 2CD383162C01C8300085B047 /* LoginViewController_DI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginViewController_DI.swift; sourceTree = ""; }; + 2CD383182C01C83C0085B047 /* ViewModelType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewModelType.swift; sourceTree = ""; }; + 2CD3831A2C01C8470085B047 /* LoginViewModel_Rx.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginViewModel_Rx.swift; sourceTree = ""; }; + 2CD3831C2C01C8530085B047 /* LoginViewController_Rx.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginViewController_Rx.swift; sourceTree = ""; }; + 2CD3831E2C01C8630085B047 /* LoginViewController_LiveCodingTemplete.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginViewController_LiveCodingTemplete.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 2CD382FD2C01C7E00085B047 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 2CD383222C01C8BB0085B047 /* SnapKit in Frameworks */, + 2CD383152C01C7F80085B047 /* RxSwift in Frameworks */, + 2CD383132C01C7F80085B047 /* RxCocoa in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 2CD382F72C01C7E00085B047 = { + isa = PBXGroup; + children = ( + 2CD383022C01C7E00085B047 /* Week7 */, + 2CD383012C01C7E00085B047 /* Products */, + ); + sourceTree = ""; + }; + 2CD383012C01C7E00085B047 /* Products */ = { + isa = PBXGroup; + children = ( + 2CD383002C01C7E00085B047 /* Week7.app */, + ); + name = Products; + sourceTree = ""; + }; + 2CD383022C01C7E00085B047 /* Week7 */ = { + isa = PBXGroup; + children = ( + 2CD383032C01C7E00085B047 /* Week7App.swift */, + 2CD383052C01C7E00085B047 /* ContentView.swift */, + 2CD383072C01C7E30085B047 /* Assets.xcassets */, + 2CD383092C01C7E30085B047 /* Preview Content */, + 2CD383162C01C8300085B047 /* LoginViewController_DI.swift */, + 2CD383182C01C83C0085B047 /* ViewModelType.swift */, + 2CD3831A2C01C8470085B047 /* LoginViewModel_Rx.swift */, + 2CD3831C2C01C8530085B047 /* LoginViewController_Rx.swift */, + 2CD3831E2C01C8630085B047 /* LoginViewController_LiveCodingTemplete.swift */, + ); + path = Week7; + sourceTree = ""; + }; + 2CD383092C01C7E30085B047 /* Preview Content */ = { + isa = PBXGroup; + children = ( + 2CD3830A2C01C7E30085B047 /* Preview Assets.xcassets */, + ); + path = "Preview Content"; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 2CD382FF2C01C7E00085B047 /* Week7 */ = { + isa = PBXNativeTarget; + buildConfigurationList = 2CD3830E2C01C7E30085B047 /* Build configuration list for PBXNativeTarget "Week7" */; + buildPhases = ( + 2CD382FC2C01C7E00085B047 /* Sources */, + 2CD382FD2C01C7E00085B047 /* Frameworks */, + 2CD382FE2C01C7E00085B047 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Week7; + packageProductDependencies = ( + 2CD383122C01C7F80085B047 /* RxCocoa */, + 2CD383142C01C7F80085B047 /* RxSwift */, + 2CD383212C01C8BB0085B047 /* SnapKit */, + ); + productName = Week7; + productReference = 2CD383002C01C7E00085B047 /* Week7.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 2CD382F82C01C7E00085B047 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1530; + LastUpgradeCheck = 1530; + TargetAttributes = { + 2CD382FF2C01C7E00085B047 = { + CreatedOnToolsVersion = 15.3; + }; + }; + }; + buildConfigurationList = 2CD382FB2C01C7E00085B047 /* Build configuration list for PBXProject "Week7" */; + compatibilityVersion = "Xcode 14.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 2CD382F72C01C7E00085B047; + packageReferences = ( + 2CD383112C01C7F80085B047 /* XCRemoteSwiftPackageReference "RxSwift" */, + 2CD383202C01C8BB0085B047 /* XCRemoteSwiftPackageReference "SnapKit" */, + ); + productRefGroup = 2CD383012C01C7E00085B047 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 2CD382FF2C01C7E00085B047 /* Week7 */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 2CD382FE2C01C7E00085B047 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 2CD3830B2C01C7E30085B047 /* Preview Assets.xcassets in Resources */, + 2CD383082C01C7E30085B047 /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 2CD382FC2C01C7E00085B047 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 2CD3831D2C01C8530085B047 /* LoginViewController_Rx.swift in Sources */, + 2CD3831F2C01C8630085B047 /* LoginViewController_LiveCodingTemplete.swift in Sources */, + 2CD3831B2C01C8470085B047 /* LoginViewModel_Rx.swift in Sources */, + 2CD383192C01C83C0085B047 /* ViewModelType.swift in Sources */, + 2CD383172C01C8300085B047 /* LoginViewController_DI.swift in Sources */, + 2CD383062C01C7E00085B047 /* ContentView.swift in Sources */, + 2CD383042C01C7E00085B047 /* Week7App.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 2CD3830C2C01C7E30085B047 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 17.4; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 2CD3830D2C01C7E30085B047 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 17.4; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 2CD3830F2C01C7E30085B047 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"Week7/Preview Content\""; + DEVELOPMENT_TEAM = MQ4ZG67BX3; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.osy.Week7; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 2CD383102C01C7E30085B047 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"Week7/Preview Content\""; + DEVELOPMENT_TEAM = MQ4ZG67BX3; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.osy.Week7; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 2CD382FB2C01C7E00085B047 /* Build configuration list for PBXProject "Week7" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 2CD3830C2C01C7E30085B047 /* Debug */, + 2CD3830D2C01C7E30085B047 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 2CD3830E2C01C7E30085B047 /* Build configuration list for PBXNativeTarget "Week7" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 2CD3830F2C01C7E30085B047 /* Debug */, + 2CD383102C01C7E30085B047 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + 2CD383112C01C7F80085B047 /* XCRemoteSwiftPackageReference "RxSwift" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/ReactiveX/RxSwift"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 6.7.1; + }; + }; + 2CD383202C01C8BB0085B047 /* XCRemoteSwiftPackageReference "SnapKit" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/SnapKit/SnapKit"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 5.7.1; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 2CD383122C01C7F80085B047 /* RxCocoa */ = { + isa = XCSwiftPackageProductDependency; + package = 2CD383112C01C7F80085B047 /* XCRemoteSwiftPackageReference "RxSwift" */; + productName = RxCocoa; + }; + 2CD383142C01C7F80085B047 /* RxSwift */ = { + isa = XCSwiftPackageProductDependency; + package = 2CD383112C01C7F80085B047 /* XCRemoteSwiftPackageReference "RxSwift" */; + productName = RxSwift; + }; + 2CD383212C01C8BB0085B047 /* SnapKit */ = { + isa = XCSwiftPackageProductDependency; + package = 2CD383202C01C8BB0085B047 /* XCRemoteSwiftPackageReference "SnapKit" */; + productName = SnapKit; + }; +/* End XCSwiftPackageProductDependency section */ + }; + rootObject = 2CD382F82C01C7E00085B047 /* Project object */; +} diff --git a/Week7/Week7.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Week7/Week7.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/Week7/Week7.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/Week7/Week7.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Week7/Week7.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/Week7/Week7.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Week7/Week7.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Week7/Week7.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 0000000..f5a2d61 --- /dev/null +++ b/Week7/Week7.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,24 @@ +{ + "originHash" : "427750f1c2e7dfa07881e8d92d79b7f6701ced4a17ec11b439e5c84172a915b3", + "pins" : [ + { + "identity" : "rxswift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/ReactiveX/RxSwift", + "state" : { + "revision" : "b06a8c8596e4c3e8e7788e08e720e3248563ce6a", + "version" : "6.7.1" + } + }, + { + "identity" : "snapkit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/SnapKit/SnapKit", + "state" : { + "revision" : "2842e6e84e82eb9a8dac0100ca90d9444b0307f4", + "version" : "5.7.1" + } + } + ], + "version" : 3 +} diff --git a/Week7/Week7.xcodeproj/project.xcworkspace/xcuserdata/osy.xcuserdatad/UserInterfaceState.xcuserstate b/Week7/Week7.xcodeproj/project.xcworkspace/xcuserdata/osy.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000..002c56d Binary files /dev/null and b/Week7/Week7.xcodeproj/project.xcworkspace/xcuserdata/osy.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/Week7/Week7.xcodeproj/xcuserdata/osy.xcuserdatad/xcschemes/xcschememanagement.plist b/Week7/Week7.xcodeproj/xcuserdata/osy.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000..2beaf82 --- /dev/null +++ b/Week7/Week7.xcodeproj/xcuserdata/osy.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,56 @@ + + + + + SchemeUserState + + Rx (Playground) 1.xcscheme + + isShown + + orderHint + 2 + + Rx (Playground) 2.xcscheme + + isShown + + orderHint + 3 + + Rx (Playground).xcscheme + + isShown + + orderHint + 1 + + SnapKitPlayground (Playground) 1.xcscheme + + isShown + + orderHint + 5 + + SnapKitPlayground (Playground) 2.xcscheme + + isShown + + orderHint + 6 + + SnapKitPlayground (Playground).xcscheme + + isShown + + orderHint + 4 + + Week7.xcscheme_^#shared#^_ + + orderHint + 0 + + + + diff --git a/Week7/Week7/Assets.xcassets/AccentColor.colorset/Contents.json b/Week7/Week7/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 0000000..eb87897 --- /dev/null +++ b/Week7/Week7/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Week7/Week7/Assets.xcassets/AppIcon.appiconset/Contents.json b/Week7/Week7/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..13613e3 --- /dev/null +++ b/Week7/Week7/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,13 @@ +{ + "images" : [ + { + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Week7/Week7/Assets.xcassets/Contents.json b/Week7/Week7/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Week7/Week7/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Week7/Week7/ContentView.swift b/Week7/Week7/ContentView.swift new file mode 100644 index 0000000..bd42143 --- /dev/null +++ b/Week7/Week7/ContentView.swift @@ -0,0 +1,24 @@ +// +// ContentView.swift +// Week7 +// +// Created by 오서영 on 5/25/24. +// + +import SwiftUI + +struct ContentView: View { + var body: some View { + VStack { + Image(systemName: "globe") + .imageScale(.large) + .foregroundStyle(.tint) + Text("Hello, world!") + } + .padding() + } +} + +#Preview { + ContentView() +} diff --git a/Week7/Week7/LoginViewController_DI.swift b/Week7/Week7/LoginViewController_DI.swift new file mode 100644 index 0000000..b53185e --- /dev/null +++ b/Week7/Week7/LoginViewController_DI.swift @@ -0,0 +1,123 @@ +// +// LoginViewController_DI.swift +// 34th-Sopt-Seminar +// +// Created by 류희재 on 5/22/24. +// + +import UIKit +import SnapKit + + +import UIKit + +protocol LoginViewModelType { + var isValid: ObservablePattern { get } + var errMessage: ObservablePattern { get } + + func checkValid(id: String?, password: String?) +} + +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 + + } +} + + +import UIKit +import SnapKit + +final class LoginViewController_DI: UIViewController { + + private let rootView = LoginView() + private let viewModel: LoginViewModelType + + override func loadView() { + self.view = rootView + } + + init(viewModel: 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/Week7/Week7/LoginViewController_LiveCodingTemplete.swift b/Week7/Week7/LoginViewController_LiveCodingTemplete.swift new file mode 100644 index 0000000..f537b12 --- /dev/null +++ b/Week7/Week7/LoginViewController_LiveCodingTemplete.swift @@ -0,0 +1,48 @@ +// +// LoginViewController_LiveCoding.swift +// 34th-Sopt-Seminar +// +// Created by 류희재 on 5/24/24. +// + +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/Week7/Week7/LoginViewController_Rx.swift b/Week7/Week7/LoginViewController_Rx.swift new file mode 100644 index 0000000..95e93ac --- /dev/null +++ b/Week7/Week7/LoginViewController_Rx.swift @@ -0,0 +1,68 @@ +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 + bindViewModel() + } + + private func bindViewModel() { + let input = LoginViewModel_Rx.Input( + idTextFieldDidChangeEvent: rootView.idTextField.rx.text.asObservable(), + passwordTextFieldDidChangeEvent: rootView.passwordTextField.rx.text.asObservable(), + loginButtonDidTapEvent: rootView.loginButton.rx.tap.asObservable() + ) + + let output = viewModel.transform(from: input, disposeBag: disposeBag) + + 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/Week7/Week7/LoginViewModel_Rx.swift b/Week7/Week7/LoginViewModel_Rx.swift new file mode 100644 index 0000000..29c5778 --- /dev/null +++ b/Week7/Week7/LoginViewModel_Rx.swift @@ -0,0 +1,134 @@ +// +// 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 idTextFieldDidChangeEvent: Observable + let passwordTextFieldDidChangeEvent: Observable + let loginButtonDidTapEvent: Observable + } + + struct Output { + var isValid = PublishRelay() + var errMessage = PublishRelay() + } + + func transform(from input: Input, disposeBag: DisposeBag) -> Output { + let output = Output() + + input.idTextFieldDidChangeEvent.subscribe(onNext: { [weak self] id in + self?.idText = id + }).disposed(by: disposeBag) + + input.passwordTextFieldDidChangeEvent.subscribe(onNext: { [weak self] password in + self?.passwordText = password + }).disposed(by: disposeBag) + + input.loginButtonDidTapEvent.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 + } +} + + + + +// +// 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 idTextFieldDidChangeEvent: Observable + let passwordTextFieldDidChangeEvent: Observable + let loginButtonDidTapEvent: Observable + } + + struct Output { + var isValid = PublishRelay() + var errMessage = PublishRelay() + } + + func transform(from input: Input, disposeBag: DisposeBag) -> Output { + let output = Output() + + input.idTextFieldDidChangeEvent.subscribe(onNext: { [weak self] id in + self?.idText = id + }).disposed(by: disposeBag) + + input.passwordTextFieldDidChangeEvent.subscribe(onNext: { [weak self] password in + self?.passwordText = password + }).disposed(by: disposeBag) + + input.loginButtonDidTapEvent.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/Week7/Week7/Preview Content/Preview Assets.xcassets/Contents.json b/Week7/Week7/Preview Content/Preview Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Week7/Week7/Preview Content/Preview Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Week7/Week7/ViewModelType.swift b/Week7/Week7/ViewModelType.swift new file mode 100644 index 0000000..91ae7ca --- /dev/null +++ b/Week7/Week7/ViewModelType.swift @@ -0,0 +1,8 @@ +import RxSwift + +protocol ViewModelType { + associatedtype Input + associatedtype Output + + func transform(from input: Input, disposeBag: RxSwift.DisposeBag) -> Output +} diff --git a/Week7/Week7/Week7App.swift b/Week7/Week7/Week7App.swift new file mode 100644 index 0000000..32305b7 --- /dev/null +++ b/Week7/Week7/Week7App.swift @@ -0,0 +1,17 @@ +// +// Week7App.swift +// Week7 +// +// Created by 오서영 on 5/25/24. +// + +import SwiftUI + +@main +struct Week7App: App { + var body: some Scene { + WindowGroup { + ContentView() + } + } +}