Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Refactor] Observable 적용 + 피드백 반영 #33

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
12 changes: 8 additions & 4 deletions AppStore-UIKit/35-seminar.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
1502973B2CDB0C9700281377 /* DetailFeedbackCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1502973A2CDB0C9700281377 /* DetailFeedbackCollectionViewCell.swift */; };
1506C1B42CFF02EB00C084C2 /* RxCocoa in Frameworks */ = {isa = PBXBuildFile; productRef = 1506C1B32CFF02EB00C084C2 /* RxCocoa */; };
152919B72CB10FE200438E2B /* SnapKit in Frameworks */ = {isa = PBXBuildFile; productRef = 152919B62CB10FE200438E2B /* SnapKit */; };
15586B622D2578A200100D05 /* ObservablePattern.swift in Sources */ = {isa = PBXBuildFile; fileRef = 15586B612D2578A200100D05 /* ObservablePattern.swift */; };
156CD7F62CD77D4800F47DA0 /* CategoryExploreView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 156CD7F52CD77D4800F47DA0 /* CategoryExploreView.swift */; };
156CD7F82CD77FC800F47DA0 /* ExploreListCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 156CD7F72CD77FC800F47DA0 /* ExploreListCollectionViewCell.swift */; };
156CD7FA2CD785B000F47DA0 /* AppListCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 156CD7F92CD785B000F47DA0 /* AppListCellView.swift */; };
Expand Down Expand Up @@ -62,7 +63,7 @@
15EAC4732CDDDB7D00419E14 /* LoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 15EAC4722CDDDB7D00419E14 /* LoginViewController.swift */; };
15EAC4752CDDDB8A00419E14 /* LoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 15EAC4742CDDDB8A00419E14 /* LoginView.swift */; };
15EAC4772CDDEFB800419E14 /* EasyAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 15EAC4762CDDEFB800419E14 /* EasyAlert.swift */; };
15EAC4792CDDF0E400419E14 /* LoginStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 15EAC4782CDDF0E400419E14 /* LoginStatus.swift */; };
15EAC4792CDDF0E400419E14 /* LoginModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 15EAC4782CDDF0E400419E14 /* LoginModel.swift */; };
15EAC47C2CDE282A00419E14 /* RegisterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 15EAC47B2CDE282A00419E14 /* RegisterView.swift */; };
15EAC47E2CDE283300419E14 /* RegisterViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 15EAC47D2CDE283300419E14 /* RegisterViewController.swift */; };
15EAC4832CDE603E00419E14 /* HobbyViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 15EAC4822CDE603E00419E14 /* HobbyViewController.swift */; };
Expand Down Expand Up @@ -100,6 +101,7 @@
/* Begin PBXFileReference section */
1502973A2CDB0C9700281377 /* DetailFeedbackCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailFeedbackCollectionViewCell.swift; sourceTree = "<group>"; };
152919992CB101BA00438E2B /* 35-seminar.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "35-seminar.app"; sourceTree = BUILT_PRODUCTS_DIR; };
15586B612D2578A200100D05 /* ObservablePattern.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObservablePattern.swift; sourceTree = "<group>"; };
156CD7F52CD77D4800F47DA0 /* CategoryExploreView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CategoryExploreView.swift; sourceTree = "<group>"; };
156CD7F72CD77FC800F47DA0 /* ExploreListCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExploreListCollectionViewCell.swift; sourceTree = "<group>"; };
156CD7F92CD785B000F47DA0 /* AppListCellView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppListCellView.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -153,7 +155,7 @@
15EAC4722CDDDB7D00419E14 /* LoginViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginViewController.swift; sourceTree = "<group>"; };
15EAC4742CDDDB8A00419E14 /* LoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginView.swift; sourceTree = "<group>"; };
15EAC4762CDDEFB800419E14 /* EasyAlert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EasyAlert.swift; sourceTree = "<group>"; };
15EAC4782CDDF0E400419E14 /* LoginStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginStatus.swift; sourceTree = "<group>"; };
15EAC4782CDDF0E400419E14 /* LoginModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginModel.swift; sourceTree = "<group>"; };
15EAC47B2CDE282A00419E14 /* RegisterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegisterView.swift; sourceTree = "<group>"; };
15EAC47D2CDE283300419E14 /* RegisterViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegisterViewController.swift; sourceTree = "<group>"; };
15EAC4822CDE603E00419E14 /* HobbyViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HobbyViewController.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -351,6 +353,7 @@
15728FC42CCCAF0100E1E151 /* Feedback.swift */,
15728FC62CCCAF3100E1E151 /* StarColor.swift */,
15F923B72CE21F8900B6F49A /* UserData.swift */,
15586B612D2578A200100D05 /* ObservablePattern.swift */,
);
path = Entity;
sourceTree = "<group>";
Expand Down Expand Up @@ -617,7 +620,7 @@
15FB94DA2D23EA8600000279 /* Model */ = {
isa = PBXGroup;
children = (
15EAC4782CDDF0E400419E14 /* LoginStatus.swift */,
15EAC4782CDDF0E400419E14 /* LoginModel.swift */,
);
path = Model;
sourceTree = "<group>";
Expand Down Expand Up @@ -753,7 +756,7 @@
15EECEBB2CF9856100E3FAA2 /* SUHobbyView.swift in Sources */,
15F9EE132CD6072B00172E82 /* Week4LoginViewController.swift in Sources */,
157119DA2CBE96BB00362252 /* ContentLabel.swift in Sources */,
15EAC4792CDDF0E400419E14 /* LoginStatus.swift in Sources */,
15EAC4792CDDF0E400419E14 /* LoginModel.swift in Sources */,
15EAC4852CDE605500419E14 /* HobbyTableViewCell.swift in Sources */,
1590A6432CBE6C6A00FB32AE /* Week1DetailViewController.swift in Sources */,
15769B602CD13BB8000DBAD3 /* Week3Photo.swift in Sources */,
Expand All @@ -765,6 +768,7 @@
15EC305D2CCB792300A0480B /* FeedbackWriteViewController.swift in Sources */,
15F9EE042CD5F8D200172E82 /* NetworkError.swift in Sources */,
15F9EE0A2CD5FBC300172E82 /* RegisterDTO.swift in Sources */,
15586B622D2578A200100D05 /* ObservablePattern.swift in Sources */,
15769B5C2CD13A25000DBAD3 /* Week3PhotoViewController.swift in Sources */,
15EAC45C2CDD32FF00419E14 /* UICollectionView+Extension.swift in Sources */,
15F4FD072CC73D0A00C99A20 /* StarStackView.swift in Sources */,
Expand Down
6 changes: 3 additions & 3 deletions AppStore-UIKit/35-seminar/Data/UserDefaultsManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ class UserDefaultsManager {
UserDefaults.standard.set(username, forKey: UserDefaultsKeys.username)
}

static func registerLoginData(loginData: LoginDTO, token: String) {
UserDefaults.standard.set(loginData.username, forKey: UserDefaultsKeys.username)
UserDefaults.standard.set(loginData.password, forKey: UserDefaultsKeys.password)
static func registerLoginData(loginInfo: LoginInfo, token: String) {
UserDefaults.standard.set(loginInfo.username, forKey: UserDefaultsKeys.username)
UserDefaults.standard.set(loginInfo.password, forKey: UserDefaultsKeys.password)
UserDefaults.standard.set(token, forKey: UserDefaultsKeys.token)
}

Expand Down
29 changes: 29 additions & 0 deletions AppStore-UIKit/35-seminar/Entity/ObservablePattern.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//
// Observable.swift
// 35-seminar
//
// Created by 김유림 on 1/1/25.
//

import Foundation

final class ObservablePattern<T: Equatable> {

var value: T? {
didSet {
self.listener?(value)
}
}

init(_ value: T?) {
self.value = value
}

private var listener: ((T?) -> Void)?

func bind(_ listener: @escaping (T?) -> Void) {
listener(value)
self.listener = listener
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,15 @@

import Foundation

class LoginStatus {
struct LoginStatus {
// 로그인 상태 설정: 로그아웃했을 때 자동으로 홈화면으로 이동되는 것 방지
static var login: Bool = true

static var autoLogin: Bool = false
}


struct LoginInfo {
var username: String
var password: String
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ class LoginView: BaseView {
private var verticalStackView = UIStackView()
private var iconImageView = UIImageView()

var usernameTextField = UITextField()
var passwordTextField = UITextField()
private var usernameTextField = UITextField()
private var passwordTextField = UITextField()

private let autoLoginCheckStackView = UIStackView()
private let autoLoginEmptyView = UIView()
Expand All @@ -26,7 +26,7 @@ class LoginView: BaseView {
let registerButton = UIButton()


// MARK: - Methods
// MARK: - Private Methods

override func setDelegate() { }

Expand Down Expand Up @@ -135,6 +135,33 @@ class LoginView: BaseView {
}
}

}


// MARK: - Internal Methods

extension LoginView {

func getUsername() -> String? {
return usernameTextField.text
}

func setUsername(text: String?) {
usernameTextField.text = text
}

func getPassword() -> String? {
return passwordTextField.text
}

func setPassword(text: String?) {
passwordTextField.text = text
}

func setUsernameDelegate(_ delegate: UITextFieldDelegate) {
usernameTextField.delegate = delegate
}

func updateAutoLoginCheckButton(autoLogin: Bool) {
if autoLogin {
autoLoginCheckButton.configureButton(systemName: "checkmark.square.fill", foregroundColor: .systemGray5)
Expand All @@ -143,20 +170,4 @@ class LoginView: BaseView {
}
}

func returnInputs() -> LoginDTO? {
guard let username = usernameTextField.text,
let password = passwordTextField.text,
!username.isEmpty, // TextField가 비어있으면 nil이 아니라 ""이기 때문에 필요.
!password.isEmpty
else {
return nil
}
return LoginDTO(username: username, password: password)
}

func bind(username: String, password: String, autoLogin: Bool) {
usernameTextField.text = username
passwordTextField.text = password
updateAutoLoginCheckButton(autoLogin: autoLogin)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,13 @@ class LoginViewController: BaseViewController {
view = loginView
}

override func viewDidLoad() {
super.viewDidLoad()
conductAutoLogin()
}

override func setDelegate() {
loginView.usernameTextField.delegate = self
loginView.setUsernameDelegate(self)
}

override func setAddTarget() {
Expand All @@ -32,36 +37,72 @@ class LoginViewController: BaseViewController {
}

override func bind() {
// auto login
let userData = UserDefaultsManager.fetchUserData()
loginViewModel.autoLogin()

loginView.bind(username: userData.username,
password: userData.password,
autoLogin: userData.autoLogin)
loginViewModel.usernameBinding.bind { [weak self] username in
guard let self = self else { return }
loginView.setUsername(text: username)
}

if userData.autoLogin {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
self.conductLogin()
}
loginViewModel.passwordBinding.bind { [weak self] password in
guard let self = self else { return }
loginView.setPassword(text: password)
}

loginViewModel.isAutoLogin.bind { [weak self] isAutoLogin in
guard let self = self,
let isAutoLogin = isAutoLogin else { return }
loginView.updateAutoLoginCheckButton(autoLogin: isAutoLogin)
}

loginViewModel.isLoginSuccess.bind { [weak self] isLoginSuccess in
guard let self = self,
let isLoginSuccess = isLoginSuccess else { return }
isLoginSuccess ? navigateToMainScreen() : EasyAlert.showAlert(title: "로그인 실패",
message: loginViewModel.loginErrorMessage,
vc: self)
}
}

private func handleLoginInfo() -> LoginInfo? {
guard let username = loginView.getUsername(),
let password = loginView.getPassword(),
Comment on lines +68 to +69
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

전 리뷰를 적용한다면, 여기서 그냥 let password = loginView.passwordTextField.text으로 접근해주면 돼용

!username.isEmpty, // TextField가 비어있으면 nil이 아니라 ""이기 때문에 필요.
!password.isEmpty else {
return nil
}

return LoginInfo(username: username, password: password)
}

private func conductLogin() {
guard let loginData: LoginDTO = loginView.returnInputs() else {
guard let loginInfo = handleLoginInfo() else {
EasyAlert.showAlert(
title: "로그인 실패",
message: "username과 password를 정확히 입력하세요.",
vc: self)
return
}

loginViewModel.login(strongSelf: self, loginData: loginData)
loginViewModel.login(loginInfo)
}

private func conductAutoLogin() {
if loginViewModel.isAutoLogin.value ?? false {
conductLogin()
}
}

private func navigateToMainScreen() {
let tabBarController = TabBarController()
tabBarController.modalPresentationStyle = .fullScreen
self.present(tabBarController, animated: true)
}

@objc func tappedAutoLoginButton() {
let autoLogin = UserDefaultsManager.fetchAutoLogin()
UserDefaultsManager.updateAutoLogin(autoLogin: !autoLogin)
loginView.updateAutoLoginCheckButton(autoLogin: !autoLogin)
loginViewModel.isAutoLogin.value = !autoLogin
}

@objc func tappedLoginButton() {
Expand All @@ -73,12 +114,14 @@ class LoginViewController: BaseViewController {
let registerVC = RegisterViewController()
self.present(registerVC, animated: true)
}

}


// MARK: - Extensions

extension LoginViewController: UITextFieldDelegate {

func textField(_ textField: UITextField,
shouldChangeCharactersIn range: NSRange,
replacementString string: String) -> Bool {
Expand All @@ -93,4 +136,5 @@ extension LoginViewController: UITextFieldDelegate {
}
return true
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -9,34 +9,57 @@ import UIKit

class LoginViewModel {

let userData = UserDefaultsManager.fetchUserData()
var isAutoLogin: ObservablePattern<Bool> = ObservablePattern(nil)
var usernameBinding: ObservablePattern<String> = ObservablePattern(nil)
var passwordBinding: ObservablePattern<String> = ObservablePattern(nil)
var isLoginSuccess: ObservablePattern<Bool> = ObservablePattern(nil)
var loginErrorMessage: String? = nil


// MARK: - Methods

func login(strongSelf: UIViewController, loginData: LoginDTO) {
init() {
setSavedLoginInfo()
}

func setSavedLoginInfo() {
usernameBinding.value = userData.username
passwordBinding.value = userData.password
isAutoLogin.value = userData.autoLogin
}

func autoLogin() {
if isAutoLogin.value ?? false {
let username = userData.username
let password = userData.password
let loginInfo = LoginInfo(username: username, password: password)

login(loginInfo)
}
}

func login(_ loginInfo: LoginInfo) {
LoginService.shared.login(
username: loginData.username,
password: loginData.password) { [weak self] result in
username: loginInfo.username,
password: loginInfo.password) { [weak self] result in
guard let self = self else { return }
handleLoginResult(strongSelf, result: result, loginData: loginData)
handleLoginResult(result: result, loginInfo: loginInfo)
}
}

private func handleLoginResult(_ strongSelf: UIViewController,
result: Result<String, NetworkError>,
loginData: LoginDTO) {
private func handleLoginResult(result: Result<String, NetworkError>,
loginInfo: LoginInfo) {
switch result {
case .success(let token):
UserDefaultsManager
.registerLoginData(loginData: loginData, token: token)
navigateToMainScreen(strongSelf)
.registerLoginData(loginInfo: loginInfo, token: token)
isLoginSuccess.value = true

case .failure(let error):
let message = error.errorMessage
EasyAlert.showAlert(title: "로그인 실패", message: message, vc: strongSelf)
loginErrorMessage = error.errorMessage
isLoginSuccess.value = false
}
}

private func navigateToMainScreen(_ strongSelf: UIViewController) {
let tabBarController = TabBarController()
tabBarController.modalPresentationStyle = .fullScreen
strongSelf.present(tabBarController, animated: true)
}
}