-
Notifications
You must be signed in to change notification settings - Fork 0
[Refactor] 온보딩 및 프로필 편집 화면 코디네이터 패턴 적용, 코드 구조 및 의존성 개선 #282
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
Changes from all commits
d58795f
acf7f00
96d57b0
c393cb2
7f4c449
c2b44c1
20e260d
2cfe84e
50148b5
ec0bf0b
6c19c5d
1b6663f
fbd1e7c
6ca3d61
545d21c
fc7ca43
ed48bd6
4d7c97e
8dd6a94
6ee9176
83dbe61
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
This file was deleted.
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,160 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||
| // | ||||||||||||||||||||||||||||||||||||||||||||||||
| // PhotoPickerHelper.swift | ||||||||||||||||||||||||||||||||||||||||||||||||
| // Wable-iOS | ||||||||||||||||||||||||||||||||||||||||||||||||
| // | ||||||||||||||||||||||||||||||||||||||||||||||||
| // Created by YOUJIM on 10/1/25. | ||||||||||||||||||||||||||||||||||||||||||||||||
| // | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| import Combine | ||||||||||||||||||||||||||||||||||||||||||||||||
| import Photos | ||||||||||||||||||||||||||||||||||||||||||||||||
| import PhotosUI | ||||||||||||||||||||||||||||||||||||||||||||||||
| import UIKit | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| final class PhotoPickerHelper: NSObject { | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| // MARK: - Property | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| private var onImageSelected: ((UIImage) -> Void)? | ||||||||||||||||||||||||||||||||||||||||||||||||
| private weak var presentingViewController: UIViewController? | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| // MARK: - Life Cycle | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| init(presentingViewController: UIViewController) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| self.presentingViewController = presentingViewController | ||||||||||||||||||||||||||||||||||||||||||||||||
| super.init() | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| func presentPhotoPicker(onImageSelected: @escaping (UIImage) -> Void) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| self.onImageSelected = onImageSelected | ||||||||||||||||||||||||||||||||||||||||||||||||
| showPhotoPicker() | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| // MARK: - Private Method | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| private extension PhotoPickerHelper { | ||||||||||||||||||||||||||||||||||||||||||||||||
| func showPhotoPicker() { | ||||||||||||||||||||||||||||||||||||||||||||||||
| let picker = createPhotoPicker() | ||||||||||||||||||||||||||||||||||||||||||||||||
| presentingViewController?.present(picker, animated: true) | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| func createPhotoPicker() -> PHPickerViewController { | ||||||||||||||||||||||||||||||||||||||||||||||||
| var configuration = PHPickerConfiguration() | ||||||||||||||||||||||||||||||||||||||||||||||||
| configuration.filter = .images | ||||||||||||||||||||||||||||||||||||||||||||||||
| configuration.selectionLimit = 1 | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| let picker = PHPickerViewController(configuration: configuration) | ||||||||||||||||||||||||||||||||||||||||||||||||
| picker.delegate = self | ||||||||||||||||||||||||||||||||||||||||||||||||
| return picker | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| func loadImage(from itemProvider: NSItemProvider) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| itemProvider.loadObject(ofClass: UIImage.self) { [weak self] image, _ in | ||||||||||||||||||||||||||||||||||||||||||||||||
| guard let image = image as? UIImage else { return } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| DispatchQueue.main.async { | ||||||||||||||||||||||||||||||||||||||||||||||||
| self?.onImageSelected?(image) | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| // MARK: - Helper Method | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| extension PhotoPickerHelper { | ||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+62
to
+64
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 헬퍼 메서드를 타입 메서드로 구현하신 이유를 들어보고 싶어요.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 현재 타입 메서드로 구현된 기능들의 경우 권한 요청 및 사진 저장과 관련된 기능으로, 따로 상태 저장을 할 필요가 없어 인스턴스를 생성할 필요성을 느끼지 못해 타입 메서드로 구현했습니다! |
||||||||||||||||||||||||||||||||||||||||||||||||
| static func requestPhotoLibraryAccess() -> AnyPublisher<Bool, Never> { | ||||||||||||||||||||||||||||||||||||||||||||||||
| let status = PHPhotoLibrary.authorizationStatus(for: .addOnly) | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| switch status { | ||||||||||||||||||||||||||||||||||||||||||||||||
| case .authorized, .limited: | ||||||||||||||||||||||||||||||||||||||||||||||||
| return Just(true).eraseToAnyPublisher() | ||||||||||||||||||||||||||||||||||||||||||||||||
| case .denied, .restricted: | ||||||||||||||||||||||||||||||||||||||||||||||||
| return Just(false).eraseToAnyPublisher() | ||||||||||||||||||||||||||||||||||||||||||||||||
| case .notDetermined: | ||||||||||||||||||||||||||||||||||||||||||||||||
| return Future<Bool, Never> { promise in | ||||||||||||||||||||||||||||||||||||||||||||||||
| PHPhotoLibrary.requestAuthorization(for: .addOnly) { newStatus in | ||||||||||||||||||||||||||||||||||||||||||||||||
| promise(.success(newStatus == .authorized || newStatus == .limited)) | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
| .receive(on: DispatchQueue.main) | ||||||||||||||||||||||||||||||||||||||||||||||||
| .eraseToAnyPublisher() | ||||||||||||||||||||||||||||||||||||||||||||||||
| @unknown default: | ||||||||||||||||||||||||||||||||||||||||||||||||
| return Just(false).eraseToAnyPublisher() | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| static func openSettings() { | ||||||||||||||||||||||||||||||||||||||||||||||||
| guard let url = URL(string: UIApplication.openSettingsURLString) else { return } | ||||||||||||||||||||||||||||||||||||||||||||||||
| UIApplication.shared.open(url) | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| static func showSettingsAlert( | ||||||||||||||||||||||||||||||||||||||||||||||||
| from viewController: UIViewController, | ||||||||||||||||||||||||||||||||||||||||||||||||
| title: String = "설정", | ||||||||||||||||||||||||||||||||||||||||||||||||
| message: String | ||||||||||||||||||||||||||||||||||||||||||||||||
| ) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| let alert = UIAlertController( | ||||||||||||||||||||||||||||||||||||||||||||||||
| title: title, | ||||||||||||||||||||||||||||||||||||||||||||||||
| message: message, | ||||||||||||||||||||||||||||||||||||||||||||||||
| preferredStyle: .alert | ||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| alert.addAction(UIAlertAction(title: "닫기", style: .default)) | ||||||||||||||||||||||||||||||||||||||||||||||||
| alert.addAction(UIAlertAction(title: "권한 설정하기", style: .default) { _ in | ||||||||||||||||||||||||||||||||||||||||||||||||
| openSettings() | ||||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| viewController.present(alert, animated: true) | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| static func saveImageToPhotoLibrary(_ image: UIImage) -> AnyPublisher<Void, Error> { | ||||||||||||||||||||||||||||||||||||||||||||||||
| Future { promise in | ||||||||||||||||||||||||||||||||||||||||||||||||
| PHPhotoLibrary.shared().performChanges { | ||||||||||||||||||||||||||||||||||||||||||||||||
| PHAssetChangeRequest.creationRequestForAsset(from: image) | ||||||||||||||||||||||||||||||||||||||||||||||||
| } completionHandler: { success, error in | ||||||||||||||||||||||||||||||||||||||||||||||||
| if let error = error { | ||||||||||||||||||||||||||||||||||||||||||||||||
| promise(.failure(error)) | ||||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||||
| promise(.success(())) | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
| .eraseToAnyPublisher() | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| static func saveImage( | ||||||||||||||||||||||||||||||||||||||||||||||||
| _ image: UIImage, | ||||||||||||||||||||||||||||||||||||||||||||||||
| onSuccess: @escaping () -> Void, | ||||||||||||||||||||||||||||||||||||||||||||||||
| onFailure: @escaping (Error) -> Void | ||||||||||||||||||||||||||||||||||||||||||||||||
| ) -> AnyCancellable { | ||||||||||||||||||||||||||||||||||||||||||||||||
| requestPhotoLibraryAccess() | ||||||||||||||||||||||||||||||||||||||||||||||||
| .flatMap { isAuthorized -> AnyPublisher<Void, Error> in | ||||||||||||||||||||||||||||||||||||||||||||||||
| guard isAuthorized else { | ||||||||||||||||||||||||||||||||||||||||||||||||
| return Empty().eraseToAnyPublisher() | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
| return saveImageToPhotoLibrary(image) | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
| .receive(on: DispatchQueue.main) | ||||||||||||||||||||||||||||||||||||||||||||||||
| .sink( | ||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+131
to
+138
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 권한 거부 시 사용자 피드백이 누락됩니다
- guard isAuthorized else {
- return Empty().eraseToAnyPublisher()
- }
+ guard isAuthorized else {
+ return Fail(
+ error: NSError(
+ domain: "PhotoPickerHelper",
+ code: -1,
+ userInfo: [NSLocalizedDescriptionKey: "Photo library access denied"]
+ )
+ )
+ .eraseToAnyPublisher()
+ }📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||
| receiveCompletion: { completion in | ||||||||||||||||||||||||||||||||||||||||||||||||
| if case .failure(let error) = completion { | ||||||||||||||||||||||||||||||||||||||||||||||||
| onFailure(error) | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||
| receiveValue: { _ in | ||||||||||||||||||||||||||||||||||||||||||||||||
| onSuccess() | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| // MARK: - PHPickerViewControllerDelegate | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| extension PhotoPickerHelper: PHPickerViewControllerDelegate { | ||||||||||||||||||||||||||||||||||||||||||||||||
| func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| picker.dismiss(animated: true) | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| guard let result = results.first else { return } | ||||||||||||||||||||||||||||||||||||||||||||||||
| loadImage(from: result.itemProvider) | ||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+155
to
+158
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 현재 코드는 순차적으로 처리하셨는데 특별한 이유가 있는지 궁금합니다. 저는
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. completionHandler의 경우 코드 뎁스가 깊어져서 정말 필요한 경우가 아니면 사용하지 않았습니다! |
||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
This file was deleted.
This file was deleted.
This file was deleted.
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.
🛠️ Refactor suggestion | 🟠 Major
🧩 Analysis chain
정규식 패턴에서 한글 자음 및 모음 처리를 확인해 주세요.
현재 정규식 패턴
^[ㄱ-ㅎ가-힣a-zA-Z0-9]+$은 한글 자음(ㄱ-ㅎ)은 포함하지만 모음(ㅏ-ㅣ)은 제외하고 있습니다. 다음 사항들을 확인해 주세요:ProfileRegisterViewModel에서 별도로 검증되는지 확인이 필요합니다.다음 스크립트로
ProfileRegisterViewModel의 길이 검증 로직을 확인해 주세요:🏁 Script executed:
Length of output: 1310
정규식 범위 및 길이 검증 보완 필요
^[ㄱ-ㅎ가-힣a-zA-Z0-9]+$가 standalone 자음(ㄱ-ㅎ)은 허용하나 모음(ㅏ-ㅣ)은 제외합니다. 자음·모음 단독 입력 허용 여부를 명확히 해주세요.validateNickname(_:)에 10자 이하 길이 제한 검증 로직을 추가해야 합니다.🤖 Prompt for AI Agents