-
Notifications
You must be signed in to change notification settings - Fork 21
[Downey] SignUpView 구조 재설계 #23
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
base: dahun-lee-daji
Are you sure you want to change the base?
[Downey] SignUpView 구조 재설계 #23
Conversation
UILabel.changeTextNColor -> .changeStyle configureCategorylabel()의 내용은 storyboard에서 시행 및 코드 삭제
FieldType, TextFormError와 관련된 UILabel을 속성으로 갖도록 정의
기존 설정을 storyboard에서 하도록 수정
textFieldCollection의 SignUpTextField으로 타입 변경에 따른 configureTextField() 내부 속성 설정
TextFormError를 반환하도록 변경. 비밀번호 검사, 이름 isNIl 로직 추가 ComplianceChecker.check의 반환값을 기준으로 UI변경 예정
delegate 내에서 ComplianceChecker를 이용하여 textField 내용 검사 해당 결과를 통해 textField와 참조중인 UILabel의 UI를 변경
정규표현식 비교 결과를 반환하는 method 반환된 결과를 본래 string과 비교하는 method
enum TextFormError으로 관리되던 에러를 protool ErrorCheckResult로 대체하여 다형성 구현
NetworkManager에 싱글톤 패턴 적용. ComplianceChecker에서 NetworkManager 인스턴스 제거 getUserList() 내부의 serialize 기능 분리
observer pattern사용하여 View-> VC로 정보 전달 -> 정보를 바탕으로 UI갱신.
findSameDimensionSignUpTextField 메서드는 generic을 사용하여 downCasting을 시도했으나, 실패함.
godrm
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PR을 보냈으니 봐달라는 의미였겠죠? 계속 개선해보세요 ㅎㅎ
| extension Notification.Name { | ||
| static let uiUpdate = Notification.Name.init("uiUpdate") | ||
| static let nextButtonEnableCheck = Notification.Name.init("nextButtonEnableCheck") | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이 노티는 누가 보내는 건가요? 누가 받는건가요?
어느 타입과 관련이 높을까요? 응집도를 높여서 타입에 선언하는 걸 고려해보세요.
extension 자체가 이 노티와 관련된 타입에 있으면 좋습니다.
| switch textField.textFieldType { | ||
| case .id: | ||
| return checkIdTextForm(with: text, closure: closure) | ||
| case .pw: | ||
| return checkPwTextForm(with: text) | ||
| case .pw2: | ||
| return checkPwDuplicated(with: textField) | ||
| case .name: | ||
| return checkNameIsNil(with: text) | ||
| default: | ||
| return ErrorResult.init() | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
잘 구현하셨습니다만 switch-case구문이 없어도 textField에 따라서 다형성으로 동작하도록 만들 수는 없을까요?
| private func generateRegexString(target : String, pattern : String) -> String { | ||
| guard let regex = try? NSRegularExpression(pattern: pattern) else { | ||
| return "" | ||
| } | ||
|
|
||
| let regexedTarget = regex.matches(in: target, options: [], range: NSRange(location: 0, length: target.utf16.count)) | ||
| let resultString = regexedTarget.map({ | ||
| String(describing: target[Range($0.range, in: target)!]) | ||
| }).joined() | ||
|
|
||
| return resultString | ||
| } | ||
|
|
||
| private func regexChekcing(target : String, pattern : String) -> Bool { | ||
| do { | ||
| let regex = try NSRegularExpression(pattern: pattern) | ||
|
|
||
| let regexedTarget = regex.matches(in: target, options: [], range: NSRange(location: 0, length: target.utf16.count)) | ||
| let resultString = regexedTarget.map({ | ||
| String(describing: target[Range($0.range, in: target)!]) | ||
| }).joined() | ||
|
|
||
| if resultString == "" { | ||
| return false | ||
| } | ||
|
|
||
| if resultString != target { | ||
| return false | ||
| } | ||
| } catch { | ||
| print(error) | ||
|
|
||
| let resultString = generateRegexString(target: target, pattern: pattern) | ||
|
|
||
| if resultString == "" { | ||
| return false |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
HugeLadderGame 예제 기억나나요? 입력에서 값을 검증하고 처리하던 것을 분리했었죠?
여기서 값을 문자열로 바꾸고 regex로 확인하는 과정이 ComplianceChecker 속성과 관련이 없으니 분리해서 별도로 테스트할 수 있는 public 함수가 되어도 되겠죠?
|
|
||
| private func checkNameIsNil(with text: String) -> ErrorCheckResult { | ||
| return text == "" ? ErrorResult.init(isError: true, message: ErrorMessage.nameIsNil.rawValue) : ErrorResult.init(isError: false, message: "") | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
check-로 시작하는 함수들 모두 꼭 여기에 구현해야 하는 것은 아닌 것 같습니다.
매개변수나 리턴타입과 관련이 높지 않을까요?
| case idIsOk = "사용 가능한 아이디입니다." | ||
| case idIneligible = "5~20자의 영문 소문자, 숫자와 특수기호(_,-)만 사용 가능합니다." | ||
| case idUsed = "이미 사용중인 아이디입니다." | ||
| case pwIsOk = "안전한 비밀번호입니다." | ||
| case pwNoNumber = "숫자를 최소 1자 이상 포함해주세요." | ||
| case pwNoSpecialCharacter = "특수문자를 최소 1자 이상 포함해주세요." | ||
| case pwNoUpperCase = "영문 대문자를 최소 1자 이상 포함해주세요." | ||
| case pwOutOfIndex = "8자 이상 16자 이하로 입력해주세요." | ||
| case pw2IsOk = "비밀번호가 일치합니다." | ||
| case pw2Different = "비밀번호가 일치하지 않습니다." | ||
| case nameIsNil = "이름은 필수 입력 항목입니다." |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
모든 에러 메시지를 다 선언하려고 하기 보다는 관련이 있는 에러끼리 묶는 것도 생각해보세요.
혹시 id, pwd 라고 붙어있는게 서로 관련이 있는게 아닐까요?
| case "NameTextField": | ||
| NotificationCenter.default.post(name: .nameTextField, object: textField) | ||
| default: | ||
| let complianceChecker = ComplianceChecker() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
기존 구조와 다르게 변경했네요. 값을 확인하는 ComplianceChecker 구체 타입에 의존하는 것이 아쉽습니다.
이런 타입이 필요하다면 생성할 때 필요한 인터페이스(프로토콜)를 넘겨주는 게 좋았을 것 같습니다.
| let complianceChecker = ComplianceChecker() | ||
|
|
||
| func textFieldDidChangeSelection(_ textField: UITextField) { | ||
| guard let textField = textField as? SignUpTextField else { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SignUpTextField가 아니어도 여기로 들어올 수 있는건가요?
| let checkResult = complianceChecker.check(target: textField, closure: { errorChecker in | ||
| NotificationCenter.default.post(name: .uiUpdate, object: textField, userInfo: ["result":errorChecker]) | ||
| }) | ||
|
|
||
| NotificationCenter.default.post(name: .uiUpdate, object: textField, userInfo: ["result":checkResult]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이런 비교자체를 델리게이트가 판단할 것인지 상위로 전달해서 처리하도록 할 것인지 고민해야 합니다.
델리게이트가 화면 업데이트가 필요하다고 판단하고 notification을 보내는 게 아니라
UITextField 델리게이트는 특정한 TextField가 물어본 textFieldDidChangeSelection()에 대해서만 처리하면 되는거죠.
델리게이트는 자기가 판단할 것만 판단하고, 이게 발생했다는 것을 다른 객체로 이벤트를 전달해서 거기서 화면을 업데이트할 지 말지 판단하는 게 좋습니다.
| guard let textField = item else { | ||
| private func findSameDimensionSignUpTextField(target : UIView) -> [SignUpTextField] { | ||
|
|
||
| let optionalFieldArray = target.superview?.subviews.filter({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
채널에 올라왔던 내용을 여기서 사용하고 있네요.
델리게이트가 모든것을 처리하라는 의미가 아닙니다. 특히나 델리게이트가 이런 메소드를 갖고 있는 것 안 좋습니다.
| enum TextFieldType : Int { | ||
| case id | ||
| case pw | ||
| case pw2 | ||
| case name | ||
| } | ||
|
|
||
| class SignUpTextField: UITextField { | ||
| var relatedLabel : UILabel? | ||
| var textFieldType : TextFieldType? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
세부 타입을 값으로 갖고 있는 것은 전형적인 추상 데이터 타입이네요.
각각 인스턴스가 생기거나 각각 클래스로 만들어서 서로 독립적인 동작을 하도록 만드는 게 좋습니다.
이렇게 만들면 어딘가에서 TextFieldType 값에 따라 switch-case를 비교해서 동작하는 코드가 생기게 됩니다.
그런 방식을 지양하세요. 서브클래스보다 복잡하고 좋지 않은 방식입니다.
쉬는날 PR 요청드려서 죄송합니다 ㅜㅜ
추가 작업 예정입니다. 머지하지 말아주세요!
작업 목록
고민과 해결
학습키워드