diff --git a/project/Plugins/ConfigurationPlugin/ProjectDescriptionHelpers/InfoPlist.swift b/project/Plugins/ConfigurationPlugin/ProjectDescriptionHelpers/InfoPlist.swift index 3f03edf7..e7737ab5 100644 --- a/project/Plugins/ConfigurationPlugin/ProjectDescriptionHelpers/InfoPlist.swift +++ b/project/Plugins/ConfigurationPlugin/ProjectDescriptionHelpers/InfoPlist.swift @@ -29,6 +29,7 @@ public enum IdleInfoPlist { ]) public static let exampleAppDefault: InfoPlist = .extendingDefault(with: [ + "Privacy - Photo Library Usage Description" : "프로필 사진 설정을 위해 사진 라이브러리에 접근합니다.", "UILaunchStoryboardName": "LaunchScreen.storyboard", "CFBundleDisplayName" : "$(BUNDLE_DISPLAY_NAME)", "UIApplicationSceneManifest": [ diff --git a/project/Plugins/DependencyPlugin/ProjectDescriptionHelpers/CenterFeatureDependency.swift b/project/Plugins/DependencyPlugin/ProjectDescriptionHelpers/CenterFeatureDependency.swift new file mode 100644 index 00000000..7dac642d --- /dev/null +++ b/project/Plugins/DependencyPlugin/ProjectDescriptionHelpers/CenterFeatureDependency.swift @@ -0,0 +1,13 @@ +// +// CenterFeatureDependency.swift +// DependencyPlugin +// +// Created by 최준영 on 6/21/24. +// + +import ProjectDescription + +public extension ModuleDependency.Presentation { + + static let CenterFeature: TargetDependency = .project(target: "CenterFeature", path: .relativeToRoot("Projects/Presentation/Feature/Center")) +} diff --git a/project/Projects/Domain/Entity/VO/AlertContentVO.swift b/project/Projects/Domain/Entity/VO/AlertContentVO.swift new file mode 100644 index 00000000..7a4d34fe --- /dev/null +++ b/project/Projects/Domain/Entity/VO/AlertContentVO.swift @@ -0,0 +1,24 @@ +// +// AlertContentVO.swift +// Entity +// +// Created by choijunios on 7/18/24. +// + +import Foundation + +public struct DefaultAlertContentVO { + + public let title: String + public let message: String + + public init(title: String, message: String) { + self.title = title + self.message = message + } + + public static let `default` = DefaultAlertContentVO( + title: "오류", + message: "동작을 수행하지 못했습니다." + ) +} diff --git a/project/Projects/Presentation/DSKit/ExampleApp/Sources/SceneDelegate.swift b/project/Projects/Presentation/DSKit/ExampleApp/Sources/SceneDelegate.swift index d9dce24f..4691396d 100644 --- a/project/Projects/Presentation/DSKit/ExampleApp/Sources/SceneDelegate.swift +++ b/project/Projects/Presentation/DSKit/ExampleApp/Sources/SceneDelegate.swift @@ -17,7 +17,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { window = UIWindow(windowScene: windowScene) - window?.rootViewController = ViewController2() + window?.rootViewController = ViewController3() window?.makeKeyAndVisible() } } diff --git a/project/Projects/Presentation/DSKit/ExampleApp/Sources/ViewController3.swift b/project/Projects/Presentation/DSKit/ExampleApp/Sources/ViewController3.swift new file mode 100644 index 00000000..bedb8399 --- /dev/null +++ b/project/Projects/Presentation/DSKit/ExampleApp/Sources/ViewController3.swift @@ -0,0 +1,63 @@ +// +// ViewController3.swift +// DSKitExampleApp +// +// Created by choijunios on 7/17/24. +// + +import UIKit +import DSKit +import RxSwift + +class ViewController3: UIViewController { + + let disposeBag = DisposeBag() + + override func viewDidLoad() { + + view.backgroundColor = .white + + view.layoutMargins = .init( + top: 0, + left: 20, + bottom: 0, + right: 20 + ) + + let field = MultiLineTextField(typography: .Body3, placeholderText: "Hello world") + + let label = IdleLabel(typography: .Body3) + label.textString = "엄청나게 긴 문장엄청나게 긴 문장엄청나게 긴 문장엄청나게 긴 문장엄청나게 긴 문장엄청나게 긴 문장엄청나게 긴 문장엄청나게 긴 문장엄청나게 긴 문장엄청나게 긴 문장엄청나게 긴 문장엄청나게 긴 문장엄청나게 긴 문장엄청나게 긴 문장엄청나게 긴 문장엄청나게 긴 문장엄청나게 긴 문장엄청나게 긴 문장엄청나게 긴 문장엄청나게 긴 문장엄청나게 긴 문장엄청나게 긴 문장엄청나게 긴 문장엄청나게 긴 문장엄청나게 긴 문장엄청나게 긴 문장엄청나게 긴 문장엄청나게 긴 문장엄청나게 긴 문장엄청나게 긴 문장엄청나게 긴 문장엄청나게 긴 문장엄청나게 긴 문장엄청나게 긴 문장엄청나게 긴 문장엄청나게 긴 문장엄청나게 긴 문장엄청나게 긴 문장엄청나게 긴 문장엄청나게 긴 문장엄청나게 긴 문장엄청나게 긴 문장엄청나게 긴 문장엄청나게 긴 문장엄청나게 긴 문장엄청나게 긴 문장" + label.numberOfLines = 0 + + let centerImageEditButton: UIButton = { + let btn = UIButton() + btn.setImage(DSKitAsset.Icons.editPhoto.image, for: .normal) + btn.isUserInteractionEnabled = true + return btn + }() + [ + field, + label, + centerImageEditButton + ] + .forEach { + $0.translatesAutoresizingMaskIntoConstraints = false + view.addSubview($0) + } + + NSLayoutConstraint.activate([ + field.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), + field.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor), + field.trailingAnchor.constraint(equalTo: view.layoutMarginsGuide.trailingAnchor), + + label.topAnchor.constraint(equalTo: field.bottomAnchor, constant: 30), + label.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor), + label.trailingAnchor.constraint(equalTo: view.layoutMarginsGuide.trailingAnchor), + + centerImageEditButton.topAnchor.constraint(equalTo: label.bottomAnchor, constant: 30), + centerImageEditButton.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor), + ]) + } +} + diff --git a/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/EditPhoto.imageset/Contents.json b/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/EditPhoto.imageset/Contents.json new file mode 100644 index 00000000..266dfb04 --- /dev/null +++ b/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/EditPhoto.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "EditImage.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/EditPhoto.imageset/EditImage.png b/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/EditPhoto.imageset/EditImage.png new file mode 100644 index 00000000..aa701b01 Binary files /dev/null and b/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/EditPhoto.imageset/EditImage.png differ diff --git a/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/location_small.imageset/Contents.json b/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/location_small.imageset/Contents.json new file mode 100644 index 00000000..f0863b1f --- /dev/null +++ b/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/location_small.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "location_image.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "location_image 1.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "location_image 2.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/location_small.imageset/location_image 1.png b/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/location_small.imageset/location_image 1.png new file mode 100644 index 00000000..734c5430 Binary files /dev/null and b/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/location_small.imageset/location_image 1.png differ diff --git a/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/location_small.imageset/location_image 2.png b/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/location_small.imageset/location_image 2.png new file mode 100644 index 00000000..734c5430 Binary files /dev/null and b/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/location_small.imageset/location_image 2.png differ diff --git a/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/location_small.imageset/location_image.png b/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/location_small.imageset/location_image.png new file mode 100644 index 00000000..734c5430 Binary files /dev/null and b/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/location_small.imageset/location_image.png differ diff --git a/project/Projects/Presentation/DSKit/Sources/Component/Button/TextButtonType2.swift b/project/Projects/Presentation/DSKit/Sources/Component/Button/TextButtonType2.swift index b78b6add..2e700488 100644 --- a/project/Projects/Presentation/DSKit/Sources/Component/Button/TextButtonType2.swift +++ b/project/Projects/Presentation/DSKit/Sources/Component/Button/TextButtonType2.swift @@ -34,7 +34,7 @@ public class TextButtonType2: UIView { private var tapGesture: UITapGestureRecognizer! - public var eventPublisher: Signal { tapGesture.rx.event.asSignal() } + public var eventPublisher: Observable { tapGesture.rx.event.map { _ in () } } public init( labelText: String, diff --git a/project/Projects/Presentation/DSKit/Sources/Component/Button/TextButtonType3.swift b/project/Projects/Presentation/DSKit/Sources/Component/Button/TextButtonType3.swift index 60beee65..8934545d 100644 --- a/project/Projects/Presentation/DSKit/Sources/Component/Button/TextButtonType3.swift +++ b/project/Projects/Presentation/DSKit/Sources/Component/Button/TextButtonType3.swift @@ -15,7 +15,7 @@ public class TextButtonType3: IdleLabel { private var tapGesture: UITapGestureRecognizer! - public var eventPublisher: Signal { tapGesture.rx.event.asSignal().map { _ in () } } + public var eventPublisher: Observable { tapGesture.rx.event.map { _ in () } } public override init(typography: Typography) { diff --git a/project/Projects/Presentation/DSKit/Sources/Component/ImageView/IdleImageView.swift b/project/Projects/Presentation/DSKit/Sources/Component/ImageView/IdleImageView.swift new file mode 100644 index 00000000..cadbfbaa --- /dev/null +++ b/project/Projects/Presentation/DSKit/Sources/Component/ImageView/IdleImageView.swift @@ -0,0 +1,29 @@ +// +// IdleImageView.swift +// DSKit +// +// Created by choijunios on 7/17/24. +// + +import UIKit + +public extension UIImageView { + + static let backButton: UIImageView = { + let view = UIImageView(image: DSKitAsset.Icons.back.image) + view.contentMode = .scaleAspectFit + return view + }() + + static let locationMark: UIImageView = { + let view = UIImageView(image: DSKitAsset.Icons.locationSmall.image) + view.contentMode = .scaleAspectFit + return view + }() + + static let editPhotoImage: UIImageView = { + let view = UIImageView(image: DSKitAsset.Icons.editPhoto.image) + view.contentMode = .scaleAspectFit + return view + }() +} diff --git a/project/Projects/Presentation/DSKit/Sources/Component/Label/IdleLabel.swift b/project/Projects/Presentation/DSKit/Sources/Component/Label/IdleLabel.swift index d895d18e..c9731718 100644 --- a/project/Projects/Presentation/DSKit/Sources/Component/Label/IdleLabel.swift +++ b/project/Projects/Presentation/DSKit/Sources/Component/Label/IdleLabel.swift @@ -32,7 +32,10 @@ public class IdleLabel: UILabel { let size = super.intrinsicContentSize - return CGSize(width: size.width, height: typography.lineHeight * CGFloat(currentLineCount)) + if currentLineCount != 0 { + return CGSize(width: size.width, height: typography.lineHeight * CGFloat(currentLineCount)) + } + return super.intrinsicContentSize } public var typography: Typography { diff --git a/project/Projects/Presentation/DSKit/Sources/Component/Label/IdleTextField.swift b/project/Projects/Presentation/DSKit/Sources/Component/Label/IdleTextField.swift index 8c3a31d1..53ec7ca2 100644 --- a/project/Projects/Presentation/DSKit/Sources/Component/Label/IdleTextField.swift +++ b/project/Projects/Presentation/DSKit/Sources/Component/Label/IdleTextField.swift @@ -18,6 +18,15 @@ public class IdleTextField: UITextField { bottom: 10, right: 24 ) + public var textString: String { + get { + return currentText + } + set { + currentText = newValue + updateText() + } + } public init(typography: Typography) { @@ -99,6 +108,9 @@ public class IdleTextField: UITextField { ) } } + private func updateText() { + self.rx.attributedText.onNext(NSAttributedString(string: textString, attributes: typography.attributes)) + } } diff --git a/project/Projects/Presentation/DSKit/Sources/Component/Stack/HStack.swift b/project/Projects/Presentation/DSKit/Sources/Component/Stack/HStack.swift new file mode 100644 index 00000000..4e511120 --- /dev/null +++ b/project/Projects/Presentation/DSKit/Sources/Component/Stack/HStack.swift @@ -0,0 +1,29 @@ +// +// HStack.swift +// DSKit +// +// Created by choijunios on 7/18/24. +// + +import UIKit + +public class HStack: UIStackView { + + public init(_ elements: [UIView], spacing: CGFloat = 0.0, alignment: UIStackView.Alignment = .center) { + + super.init(frame: .zero) + + self.spacing = spacing + self.axis = .horizontal + self.distribution = .fill + self.alignment = alignment + + elements + .forEach { + self.addArrangedSubview($0) + } + } + + required init(coder: NSCoder) { fatalError() } +} + diff --git a/project/Projects/Presentation/DSKit/Sources/Component/UIExtension/Stack.swift b/project/Projects/Presentation/DSKit/Sources/Component/Stack/VStack.swift similarity index 96% rename from project/Projects/Presentation/DSKit/Sources/Component/UIExtension/Stack.swift rename to project/Projects/Presentation/DSKit/Sources/Component/Stack/VStack.swift index d7c30d3f..aaf52921 100644 --- a/project/Projects/Presentation/DSKit/Sources/Component/UIExtension/Stack.swift +++ b/project/Projects/Presentation/DSKit/Sources/Component/Stack/VStack.swift @@ -1,5 +1,5 @@ // -// Stack.swift +// VStack.swift // DSKit // // Created by choijunios on 7/15/24. @@ -27,3 +27,4 @@ public class VStack: UIStackView { required init(coder: NSCoder) { fatalError() } } + diff --git a/project/Projects/Presentation/DSKit/Sources/Component/TextField/IdleOneLineInputField.swift b/project/Projects/Presentation/DSKit/Sources/Component/TextField/IdleOneLineInputField.swift index c04817bb..d96d779d 100644 --- a/project/Projects/Presentation/DSKit/Sources/Component/TextField/IdleOneLineInputField.swift +++ b/project/Projects/Presentation/DSKit/Sources/Component/TextField/IdleOneLineInputField.swift @@ -9,13 +9,6 @@ import UIKit import RxSwift import RxCocoa -/// 총 Height 44(42 + border(1pt)x2) -/// -/// TextBox사이즈 24 -/// inset -/// vertical: 11 -/// horizontal: 20 - public class IdleOneLineInputField: UIView { public var isEnabled: Bool = true diff --git a/project/Projects/Presentation/DSKit/Sources/Component/TextField/MultiLineTextField.swift b/project/Projects/Presentation/DSKit/Sources/Component/TextField/MultiLineTextField.swift new file mode 100644 index 00000000..02b3cfac --- /dev/null +++ b/project/Projects/Presentation/DSKit/Sources/Component/TextField/MultiLineTextField.swift @@ -0,0 +1,117 @@ +// +// MultiLineTextField.swift +// DSKit +// +// Created by choijunios on 7/17/24. +// + +import UIKit +import RxSwift +import RxCocoa + +public class MultiLineTextField: UITextView { + + private var currentText: String = "" + + public var placeholderText: String + public let typography: Typography + + public var textString: String { + get { + return currentText + } + set { + currentText = newValue + updateText() + } + } + + private let disposeBag = DisposeBag() + + public init(typography: Typography, placeholderText: String = "") { + self.placeholderText = placeholderText + self.typography = typography + + super.init(frame: .zero, textContainer: nil) + + setAppearance() + setPlaceholderText(textView: self) + addToolbar() + } + + required init?(coder: NSCoder) { fatalError() } + + func setAppearance() { + // Delegate + self.delegate = self + + // border + self.layer.borderColor = DSKitAsset.Colors.gray100.color.cgColor + self.layer.borderWidth = 1.0 + self.layer.cornerRadius = 6 + + // textContainer + self.textContainerInset = .init(top: 12, left: 16, bottom: 16, right: 16) + self.textContainer.lineFragmentPadding = 0 + + // font + self.typingAttributes = typography.attributes + + // Scroll + self.isScrollEnabled = true + } + + public func addToolbar() { + // TextField toolbar + let toolbar = UIToolbar() + toolbar.sizeToFit() + + // flexibleSpace 추가 + let flexibleSpace = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil) + + let closeButton = UIBarButtonItem() + closeButton.title = "완료" + closeButton.style = .done + toolbar.setItems([ + flexibleSpace, + closeButton + ], animated: false) + toolbar.isUserInteractionEnabled = true + + self.inputAccessoryView = toolbar + + closeButton.rx.tap.subscribe { [weak self] _ in + + self?.resignFirstResponder() + } + .disposed(by: disposeBag) + } + + private func updateText() { + self.rx.attributedText.onNext(NSAttributedString(string: textString, attributes: typography.attributes)) + } +} + +extension MultiLineTextField: UITextViewDelegate { + + // UITextViewDelegate 메서드: 텍스트 뷰가 편집을 시작할 때 호출 + public func textViewDidBeginEditing(_ textView: UITextView) { + if textView.text == placeholderText { + textView.attributedText = .none + textView.textColor = DSKitAsset.Colors.gray900.color + } + } + + // UITextViewDelegate 메서드: 텍스트 뷰가 편집을 끝낼 때 호출 + public func textViewDidEndEditing(_ textView: UITextView) { + setPlaceholderText(textView: textView) + } + + private func setPlaceholderText(textView: UITextView) { + if textView.attributedText.string.isEmpty { + textView.attributedText = self.typography.attributes.toString(placeholderText) + textView.textColor = DSKitAsset.Colors.gray200.color + } + } + +} diff --git a/project/Projects/Presentation/Feature/Auth/Sources/View/Center/CenterAuthMainViewController.swift b/project/Projects/Presentation/Feature/Auth/Sources/View/Center/CenterAuthMainViewController.swift index 5effad35..8368333f 100644 --- a/project/Projects/Presentation/Feature/Auth/Sources/View/Center/CenterAuthMainViewController.swift +++ b/project/Projects/Presentation/Feature/Auth/Sources/View/Center/CenterAuthMainViewController.swift @@ -129,7 +129,7 @@ public class CenterAuthMainViewController: DisposableViewController { loginButton .eventPublisher - .emit { [weak self] _ in + .subscribe { [weak self] _ in self?.coordinator?.parent?.login() } .disposed(by: disposeBag) diff --git a/project/Projects/Presentation/Feature/Auth/Sources/View/Center/Login/CenterLoginViewController.swift b/project/Projects/Presentation/Feature/Auth/Sources/View/Center/Login/CenterLoginViewController.swift index 74eff375..840f78bf 100644 --- a/project/Projects/Presentation/Feature/Auth/Sources/View/Center/Login/CenterLoginViewController.swift +++ b/project/Projects/Presentation/Feature/Auth/Sources/View/Center/Login/CenterLoginViewController.swift @@ -211,7 +211,7 @@ public class CenterLoginViewController: DisposableViewController { forgotPasswordButton .eventPublisher - .emit { [weak self] _ in + .subscribe { [weak self] _ in self?.coordinator?.parent?.setNewPassword() } .disposed(by: disposeBag) diff --git a/project/Projects/Presentation/Feature/Auth/Sources/View/Worker/Register/EnterAddressViewController.swift b/project/Projects/Presentation/Feature/Auth/Sources/View/Worker/Register/EnterAddressViewController.swift index f32c5c89..737e255d 100644 --- a/project/Projects/Presentation/Feature/Auth/Sources/View/Worker/Register/EnterAddressViewController.swift +++ b/project/Projects/Presentation/Feature/Auth/Sources/View/Worker/Register/EnterAddressViewController.swift @@ -177,7 +177,7 @@ where T.Input: EnterAddressInputable & CTAButtonEnableInputable, T.Output: Enter addressSearchButton .eventPublisher - .emit { [weak self] _ in + .subscribe { [weak self] _ in self?.showDaumSearchView() } .disposed(by: disposeBag) diff --git a/project/Projects/Presentation/Feature/Center/ExampleApp/Resources/LaunchScreen.storyboard b/project/Projects/Presentation/Feature/Center/ExampleApp/Resources/LaunchScreen.storyboard new file mode 100644 index 00000000..a2157a3e --- /dev/null +++ b/project/Projects/Presentation/Feature/Center/ExampleApp/Resources/LaunchScreen.storyboard @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/project/Projects/Presentation/Feature/Center/ExampleApp/Sources/AppDelegate.swift b/project/Projects/Presentation/Feature/Center/ExampleApp/Sources/AppDelegate.swift new file mode 100644 index 00000000..00267bb5 --- /dev/null +++ b/project/Projects/Presentation/Feature/Center/ExampleApp/Sources/AppDelegate.swift @@ -0,0 +1,36 @@ +// +// AppDelegate.swift +// +// +// Created by 최준영 on 6/19/24. +// + +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/project/Projects/Presentation/Feature/Center/ExampleApp/Sources/SceneDelegate.swift b/project/Projects/Presentation/Feature/Center/ExampleApp/Sources/SceneDelegate.swift new file mode 100644 index 00000000..29606e91 --- /dev/null +++ b/project/Projects/Presentation/Feature/Center/ExampleApp/Sources/SceneDelegate.swift @@ -0,0 +1,30 @@ +// +// SceneDelegate.swift +// +// +// Created by 최준영 on 6/19/24. +// + +import UIKit +import CenterFeature + +class SceneDelegate: UIResponder, UIWindowSceneDelegate { + + var window: UIWindow? + + func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { + + guard let windowScene = scene as? UIWindowScene else { return } + + + window = UIWindow(windowScene: windowScene) + + let viewModel = CenterProfileViewModel() + let viewController = CenterProfileViewController() + + viewController.bind(viewModel: viewModel) + + window?.rootViewController = viewController + window?.makeKeyAndVisible() + } +} diff --git a/project/Projects/Presentation/Feature/Center/ExampleApp/Sources/ViewController.swift b/project/Projects/Presentation/Feature/Center/ExampleApp/Sources/ViewController.swift new file mode 100644 index 00000000..e439d432 --- /dev/null +++ b/project/Projects/Presentation/Feature/Center/ExampleApp/Sources/ViewController.swift @@ -0,0 +1,29 @@ +// +// ViewController.swift +// +// +// Created by 최준영 on 6/19/24. +// + +import UIKit + +class ViewController: UIViewController { + + override func viewDidLoad() { + + let initialLabel = UILabel() + + initialLabel.text = "Example app" + + view.backgroundColor = .white + + view.addSubview(initialLabel) + initialLabel.translatesAutoresizingMaskIntoConstraints = false + + NSLayoutConstraint.activate([ + initialLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor), + initialLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor), + ]) + } +} + diff --git a/project/Projects/Presentation/Feature/Center/Project.swift b/project/Projects/Presentation/Feature/Center/Project.swift new file mode 100644 index 00000000..5c87b31d --- /dev/null +++ b/project/Projects/Presentation/Feature/Center/Project.swift @@ -0,0 +1,84 @@ +// +// Project.swift +// ProjectDescriptionHelpers +// +// Created by choijunios on 2024/07/17 +// + +import ProjectDescription +import ProjectDescriptionHelpers +import ConfigurationPlugin +import DependencyPlugin + +let project = Project( + name: "Center", + settings: .settings( + configurations: IdleConfiguration.emptyConfigurations + ), + targets: [ + + /// FeatureConcrete + .target( + name: "CenterFeature", + destinations: DeploymentSettings.platform, + product: .staticFramework, + bundleId: "$(PRODUCT_BUNDLE_IDENTIFIER)", + deploymentTargets: DeploymentSettings.deployment_version, + sources: ["Sources/**"], + resources: ["Resources/**"], + dependencies: [ + // Presentation + D.Presentation.PresentationCore, + D.Presentation.DSKit, + + // Domain + D.Domain.UseCaseInterface, + D.Domain.RepositoryInterface, + + // ThirdParty + D.ThirdParty.RxSwift, + D.ThirdParty.RxCocoa, + ], + settings: .settings( + configurations: IdleConfiguration.presentationConfigurations + ) + ), + + /// FeatureConcrete ExampleApp + .target( + name: "Center_ExampleApp", + destinations: DeploymentSettings.platform, + product: .app, + bundleId: "$(PRODUCT_BUNDLE_IDENTIFIER)", + deploymentTargets: DeploymentSettings.deployment_version, + infoPlist: IdleInfoPlist.exampleAppDefault, + sources: ["ExampleApp/Sources/**"], + resources: ["ExampleApp/Resources/**"], + dependencies: [ + .target(name: "CenterFeature"), + + D.Domain.ConcreteUseCase, + D.Data.ConcreteRepository, + ], + settings: .settings( + configurations: IdleConfiguration.presentationConfigurations + ) + ), + ], + schemes: [ + Scheme.makeSchemes( + .target("CenterFeature"), + configNames: [ + IdleConfiguration.debugConfigName, + IdleConfiguration.releaseConfigName + ] + ), + Scheme.makeSchemes( + .target("Center_ExampleApp"), + configNames: [ + IdleConfiguration.debugConfigName, + IdleConfiguration.releaseConfigName + ] + ) + ].flatMap { $0 } +) diff --git a/project/Projects/Presentation/Feature/Center/Resources/Empty.md b/project/Projects/Presentation/Feature/Center/Resources/Empty.md new file mode 100644 index 00000000..64e53d46 --- /dev/null +++ b/project/Projects/Presentation/Feature/Center/Resources/Empty.md @@ -0,0 +1,2 @@ +# <#Title#> + diff --git a/project/Projects/Presentation/Feature/Center/Sources/View/Profile/CenterProfileViewController.swift b/project/Projects/Presentation/Feature/Center/Sources/View/Profile/CenterProfileViewController.swift new file mode 100644 index 00000000..e9bb3a8b --- /dev/null +++ b/project/Projects/Presentation/Feature/Center/Sources/View/Profile/CenterProfileViewController.swift @@ -0,0 +1,506 @@ +// +// CenterProfileViewController.swift +// CenterFeature +// +// Created by choijunios on 7/17/24. +// + +import UIKit +import PresentationCore +import RxSwift +import RxCocoa +import DSKit +import Entity + +public protocol CenterProfileViewModelable where Input: CenterProfileInputable, Output: CenterProfileOutputable { + associatedtype Input + associatedtype Output + var input: Input { get } + var output: Output? { get } + + func requestData() +} + +public protocol CenterProfileInputable { + var editingButtonPressed: PublishRelay { get } + var editingFinishButtonPressed: PublishRelay { get } + var editingPhoneNumber: BehaviorRelay { get } + var editingInstruction: BehaviorRelay { get } + var editingImage: BehaviorRelay { get } +} + +public protocol CenterProfileOutputable { + var centerName: Driver { get } + var centerLocation: Driver { get } + var centerPhoneNumber: Driver { get } + var centerIntroduction: Driver { get } + var centerImage: Driver { get } + var isEditingMode: Driver { get } + var editingValidation: Driver { get } + var alert: Driver { get } +} + +public class CenterProfileViewController: DisposableViewController { + + var viewModel: (any CenterProfileViewModelable)? + + let navigationBar: NavigationBarType1 = { + let bar = NavigationBarType1(navigationTitle: "내 센터 정보") + return bar + }() + + let editingCompleteButton: TextButtonType3 = { + let btn = TextButtonType3(typography: .Subtitle2) + btn.textString = "저장" + btn.attrTextColor = DSKitAsset.Colors.orange500.color + return btn + }() + + // View + + /// Center name label + let centerNameLabel: IdleLabel = { + let label = IdleLabel(typography: .Heading1) + return label + }() + + /// Center location label + let centerLocationLabel: IdleLabel = { + let label = IdleLabel(typography: .Body2) + + return label + }() + + /// ☑️ 센터 상세정보 ☑️ + let centerDetailLabel: IdleLabel = { + let label = IdleLabel(typography: .Body2) + label.textString = "센터 상세 정보" + return label + }() + let profileEditButton: TextButtonType2 = { + let button = TextButtonType2(labelText: "수정하기") + + button.label.typography = .Body3 + button.label.attrTextColor = DSKitAsset.Colors.gray300.color + button.layoutMargins = .init(top: 5.5, left:12, bottom: 5.5, right: 12) + button.layer.cornerRadius = 16 + return button + }() + + /// ☑️ "전화번호" 라벨 ☑️ + let centerPhoneNumeberTitleLabel: IdleLabel = { + let label = IdleLabel(typography: .Subtitle4) + label.textString = "전화번호" + label.textColor = DSKitAsset.Colors.gray500.color + return label + }() + + /// 센터 전화번호가 표시되는 라벨 + let centerPhoneNumeberLabel: IdleLabel = { + let label = IdleLabel(typography: .Body3) + return label + }() + /// 센터 전화번호를 편집할 수 있는 텍스트 필드 + let centerPhoneNumeberField: MultiLineTextField = { + let textView = MultiLineTextField( + typography: .Body3, + placeholderText: "추가적으로 요구사항이 있다면 작성해주세요." + ) + textView.textContainerInset = .init(top: 10, left: 16, bottom: 10, right: 24) + textView.isScrollEnabled = false + return textView + }() + + /// ☑️ "센토 소개" 라벨 ☑️ + let centerIntroductionTitleLabel: IdleLabel = { + let label = IdleLabel(typography: .Subtitle4) + label.textString = "센터 소개" + label.textColor = DSKitAsset.Colors.gray500.color + return label + }() + + /// 센터 소개가 표시되는 라벨 + let centerIntroductionLabel: IdleLabel = { + let label = IdleLabel(typography: .Body3) + return label + }() + /// 센터 소개를 수정하는 텍스트 필드 + let centerIntroductionField: MultiLineTextField = { + let textView = MultiLineTextField( + typography: .Body3, + placeholderText: "추가적으로 요구사항이 있다면 작성해주세요." + ) + return textView + }() + + /// ☑️ "센토 사진" 라벨 ☑️ + let centerPictureLabel: IdleLabel = { + let label = IdleLabel(typography: .Subtitle4) + label.textString = "센터 사진" + label.textColor = DSKitAsset.Colors.gray500.color + return label + }() + let centerImageView: UIImageView = { + let view = UIImageView() + view.layer.cornerRadius = 6 + view.clipsToBounds = true + view.backgroundColor = DSKitAsset.Colors.gray100.color + view.contentMode = .scaleAspectFill + + /// 이미지 뷰는 버튼을 자식으로 가지는데 기본적으로 isUserInteractionEnabled값이 fale라 자식 버튼에도 영향을 미친다. + /// 따라서 이터렉션이 필요한 자식이 있는 경우 명시적으로 아래 프로퍼티값을 true로 설정해야한다. + view.isUserInteractionEnabled = true + return view + }() + let centerImageEditButton: UIButton = { + let btn = UIButton() + btn.setImage(DSKitAsset.Icons.editPhoto.image, for: .normal) + btn.isUserInteractionEnabled = true + return btn + }() + + let edtingImage: PublishRelay = .init() + + let disposeBag = DisposeBag() + + public init() { + + super.init(nibName: nil, bundle: nil) + + setApearance() + setAutoLayout() + setObservable() + } + + required init?(coder: NSCoder) { fatalError() } + + func setApearance() { + view.backgroundColor = .white + } + + func setAutoLayout() { + + // 상단 네비게이션바 세팅 + let navigationStack = HStack([ + navigationBar, + editingCompleteButton, + ]) + navigationStack.distribution = .equalSpacing + navigationStack.backgroundColor = .white + + let navigationStackBackground = UIView() + navigationStackBackground.addSubview(navigationStack) + navigationStack.translatesAutoresizingMaskIntoConstraints = false + navigationStackBackground.backgroundColor = .white + navigationStackBackground.layoutMargins = .init(top: 0, left: 12, bottom: 0, right: 28) + NSLayoutConstraint.activate([ + navigationStack.topAnchor.constraint(equalTo: navigationStackBackground.layoutMarginsGuide.topAnchor), + navigationStack.leadingAnchor.constraint(equalTo: navigationStackBackground.layoutMarginsGuide.leadingAnchor), + navigationStack.trailingAnchor.constraint(equalTo: navigationStackBackground.layoutMarginsGuide.trailingAnchor), + navigationStack.bottomAnchor.constraint(equalTo: navigationStackBackground.layoutMarginsGuide.bottomAnchor), + ]) + + let locationIcon = UIImageView.locationMark + + let centerLocationStack = HStack( + [ + locationIcon, + centerLocationLabel + ], + spacing: 2, + alignment: .center + ) + + let centerPhoneNumberStack = VStack( + [ + centerPhoneNumeberTitleLabel, + centerPhoneNumeberLabel, + centerPhoneNumeberField, + ], + spacing: 6, + alignment: .fill + ) + + let centerIntroductionStack = VStack( + [ + centerIntroductionTitleLabel, + centerIntroductionLabel, + centerIntroductionField, + ], + spacing: 6, + alignment: .fill + ) + + // 센터 이미지뷰 세팅 + centerImageView.addSubview(centerImageEditButton) + centerImageEditButton.translatesAutoresizingMaskIntoConstraints = false + + let scrollView = UIScrollView() + + let divider = UIView() + divider.backgroundColor = DSKitAsset.Colors.gray050.color + + [ + centerNameLabel, + centerLocationStack, + + divider, + + centerDetailLabel, + profileEditButton, + + centerPhoneNumberStack, + + centerIntroductionStack, + + centerPictureLabel, + centerImageView, + ].forEach { + $0.translatesAutoresizingMaskIntoConstraints = false + scrollView.addSubview($0) + } + + [ + navigationStackBackground, + scrollView + ].forEach { + $0.translatesAutoresizingMaskIntoConstraints = false + view.addSubview($0) + } + // view 서브뷰 zindex설정 + navigationStackBackground.layer.zPosition = 1 + scrollView.layer.zPosition = 0 + + // 전체 뷰 + NSLayoutConstraint.activate([ + navigationStackBackground.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), + navigationStackBackground.leadingAnchor.constraint(equalTo: view.leadingAnchor), + navigationStackBackground.trailingAnchor.constraint(equalTo: view.trailingAnchor), + + scrollView.topAnchor.constraint(equalTo: navigationStackBackground.bottomAnchor), + scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor), + scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor) + ]) + + // 뷰 고정 사이즈 + NSLayoutConstraint.activate([ + locationIcon.widthAnchor.constraint(equalToConstant: 24), + locationIcon.heightAnchor.constraint(equalTo: locationIcon.widthAnchor), + + centerImageEditButton.widthAnchor.constraint(equalToConstant: 28), + centerImageEditButton.heightAnchor.constraint(equalTo: centerImageEditButton.widthAnchor), + + centerIntroductionField.heightAnchor.constraint(equalToConstant: 156), + ]) + + let contentGuide = scrollView.contentLayoutGuide + scrollView.layoutMargins = .init(top: 0, left: 20, bottom: 0, right: 20) + scrollView.delaysContentTouches = false + + // 스크롤 뷰의 서브뷰 + NSLayoutConstraint.activate([ + + centerNameLabel.topAnchor.constraint(equalTo: contentGuide.topAnchor, constant: 25), + centerNameLabel.leadingAnchor.constraint(equalTo: scrollView.layoutMarginsGuide.leadingAnchor), + + centerLocationStack.topAnchor.constraint(equalTo: centerNameLabel.bottomAnchor, constant: 12), + centerLocationStack.leadingAnchor.constraint(equalTo: scrollView.layoutMarginsGuide.leadingAnchor), + + divider.topAnchor.constraint(equalTo: centerLocationStack.bottomAnchor, constant: 20), + divider.leadingAnchor.constraint(equalTo: view.leadingAnchor), + divider.trailingAnchor.constraint(equalTo: view.trailingAnchor), + divider.heightAnchor.constraint(equalToConstant: 8), + + centerDetailLabel.topAnchor.constraint(equalTo: divider.bottomAnchor, constant: 24), + centerDetailLabel.leadingAnchor.constraint(equalTo: scrollView.layoutMarginsGuide.leadingAnchor), + + profileEditButton.topAnchor.constraint(equalTo: divider.bottomAnchor, constant: 24), + profileEditButton.trailingAnchor.constraint(equalTo: scrollView.layoutMarginsGuide.trailingAnchor), + + centerPhoneNumberStack.topAnchor.constraint(equalTo: centerDetailLabel.bottomAnchor, constant: 20), + centerPhoneNumberStack.leadingAnchor.constraint(equalTo: scrollView.layoutMarginsGuide.leadingAnchor), + centerPhoneNumberStack.trailingAnchor.constraint(equalTo: scrollView.layoutMarginsGuide.trailingAnchor), + + centerIntroductionStack.topAnchor.constraint(equalTo: centerPhoneNumberStack.bottomAnchor, constant: 20), + centerIntroductionStack.leadingAnchor.constraint(equalTo: scrollView.layoutMarginsGuide.leadingAnchor), + centerIntroductionStack.trailingAnchor.constraint(equalTo: scrollView.layoutMarginsGuide.trailingAnchor), + + centerPictureLabel.topAnchor.constraint(equalTo: centerIntroductionStack.bottomAnchor, constant: 20), + centerPictureLabel.leadingAnchor.constraint(equalTo: scrollView.layoutMarginsGuide.leadingAnchor), + + centerImageView.topAnchor.constraint(equalTo: centerPictureLabel.bottomAnchor, constant: 20), + + centerImageView.leadingAnchor.constraint(equalTo: scrollView.layoutMarginsGuide.leadingAnchor), + centerImageView.trailingAnchor.constraint(equalTo: scrollView.layoutMarginsGuide.trailingAnchor), + centerImageView.heightAnchor.constraint(equalToConstant: 250), + centerImageView.bottomAnchor.constraint(equalTo: scrollView.contentLayoutGuide.bottomAnchor, constant: -38), + + centerImageEditButton.trailingAnchor.constraint(equalTo: centerImageView.trailingAnchor, constant: -16), + centerImageEditButton.bottomAnchor.constraint(equalTo: centerImageView.bottomAnchor, constant: -16), + ]) + } + + func setObservable() { + + centerImageEditButton + .rx.tap + .subscribe { [weak self] _ in + self?.showPhotoGalley() + } + .disposed(by: disposeBag) + } + + public func bind(viewModel: any CenterProfileViewModelable) { + + self.viewModel = viewModel + + // input + let input = viewModel.input + + profileEditButton + .eventPublisher + .bind(to: input.editingButtonPressed) + .disposed(by: disposeBag) + + editingCompleteButton + .eventPublisher + .bind(to: input.editingFinishButtonPressed) + .disposed(by: disposeBag) + + centerPhoneNumeberField.rx.text + .compactMap { $0 } + .bind(to: input.editingPhoneNumber) + .disposed(by: disposeBag) + + centerIntroductionField.rx.text + .compactMap { $0 } + .bind(to: input.editingInstruction) + .disposed(by: disposeBag) + + edtingImage + .bind(to: input.editingImage) + .disposed(by: disposeBag) + + // output + guard let output = viewModel.output else { fatalError() } + + output + .centerName + .drive(centerNameLabel.rx.textString) + .disposed(by: disposeBag) + + output + .centerLocation + .drive(centerLocationLabel.rx.textString) + .disposed(by: disposeBag) + + output + .centerPhoneNumber + .drive(centerPhoneNumeberLabel.rx.textString) + .disposed(by: disposeBag) + output + .centerPhoneNumber + .drive(centerPhoneNumeberField.rx.textString) + .disposed(by: disposeBag) + + output + .centerIntroduction + .drive(centerIntroductionLabel.rx.textString) + .disposed(by: disposeBag) + output + .centerIntroduction + .drive(centerIntroductionField.rx.textString) + .disposed(by: disposeBag) + + output + .centerImage + .drive(centerImageView.rx.image) + .disposed(by: disposeBag) + + // MARK: Edit Mode + output + .isEditingMode + .drive { [weak self] in + guard let self else { return } + + centerPhoneNumeberField.isHidden = !$0 + centerPhoneNumeberLabel.isHidden = $0 + + centerIntroductionField.isHidden = !$0 + centerIntroductionLabel.isHidden = $0 + + centerImageEditButton.isHidden = !$0 + + editingCompleteButton.isHidden = !$0 + profileEditButton.isHidden = $0 + + } + .disposed(by: disposeBag) + + output + .alert + .drive { [weak self] vo in + self?.showAlert(vo: vo) + } + .disposed(by: disposeBag) + + output + .editingValidation + .drive { _ in + // do something when editing success + } + .disposed(by: disposeBag) + + viewModel.requestData() + } + + public func showAlert(vo: DefaultAlertContentVO) { + let alret = UIAlertController(title: vo.title, message: vo.message, preferredStyle: .alert) + let close = UIAlertAction(title: "닫기", style: .default, handler: nil) + alret.addAction(close) + present(alret, animated: true, completion: nil) + } + + public func cleanUp() { + + } +} + +extension CenterProfileViewController { + + func showPhotoGalley() { + + let imagePickerVC = UIImagePickerController() + imagePickerVC.delegate = self + + if !UIImagePickerController.isSourceTypeAvailable(.photoLibrary) { + + showAlert(vo: .init( + title: "오류", + message: "사진함을 열 수 없습니다.") + ) + + return + } + + imagePickerVC.sourceType = .photoLibrary + +// let modiaTypes = UIImagePickerController.availableMediaTypes(for: .photoLibrary) + present(imagePickerVC, animated: true) + } +} + +extension CenterProfileViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate { + + public func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) { + + if let image = info[UIImagePickerController.InfoKey.originalImage] as? UIImage { + + edtingImage.accept(image) + centerImageView.image = image + + picker.dismiss(animated: true) + } + } +} diff --git a/project/Projects/Presentation/Feature/Center/Sources/ViewModel/Profile/CenterProfileViewModel.swift b/project/Projects/Presentation/Feature/Center/Sources/ViewModel/Profile/CenterProfileViewModel.swift new file mode 100644 index 00000000..0c2ec4f4 --- /dev/null +++ b/project/Projects/Presentation/Feature/Center/Sources/ViewModel/Profile/CenterProfileViewModel.swift @@ -0,0 +1,200 @@ +// +// CenterProfileViewModel.swift +// CenterFeature +// +// Created by choijunios on 7/18/24. +// + +import UIKit +import Entity +import RxSwift +import RxCocoa +import PresentationCore + +public struct ChangeCenterInformation { + let phoneNumber: String? + let introduction: String? + let image: UIImage? +} + +public class CenterProfileViewModel: CenterProfileViewModelable { + + public var input: Input + public var output: Output? = nil + + func checkModification( + prev_phoneNumber: String, + prev_introduction: String, + prev_image: UIImage) -> (String?, String?, UIImage?) + { + ( + input.editingPhoneNumber.value == prev_phoneNumber ? nil : input.editingPhoneNumber.value, + input.editingInstruction.value == prev_introduction ? nil : input.editingInstruction.value, + input.editingImage.value == prev_image ? nil : input.editingImage.value + ) + } + + public init() { + self.input = Input() + + // 최신 값들 + 버튼이 눌릴 경우 변경 로직이 실행된다. + let editingRequestResult = input + .editingFinishButtonPressed + .map({ [unowned self] _ in + self.checkModification( + prev_phoneNumber: self.input.centerPhoneNumber.value, + prev_introduction: self.input.centerIntroduction.value, + prev_image: self.input.centerImage.value + ) + }) + .flatMap { (inputs) in + + let (phoneNumber, introduction, image) = inputs + + // 변경이 발생하지 않은 곳은 nil값이 전달된다. + + // API 호출 + return Single.just(Result.success( + ChangeCenterInformation( + phoneNumber: phoneNumber, + introduction: introduction, + image: image + ) + )) + } + .share() + + // 스트림을 유지하기위해 생성한 Driver로 필수적으로 사용되지 않는다. + let editingValidation = editingRequestResult + .compactMap { $0.value } + .map { [weak input] info in + + if let phoneNumber = info.phoneNumber { + printIfDebug("✅ 전화번호 변경 반영되었음") + input?.centerPhoneNumber.accept(phoneNumber) + } + + if let introduction = info.introduction { + printIfDebug("✅ 센터소개 반영되었음") + input?.centerIntroduction.accept(introduction) + } + + if let image = info.image { + printIfDebug("✅ 센터 이미지 변경 반영되었음") + input?.centerImage.accept(image) + } + + return () + } + .asDriver(onErrorJustReturn: ()) + + enum Mode { + case editing, display + } + + let initialMode = BehaviorRelay(value: Mode.display) + + let buttonPress = Observable + .merge( + input.editingButtonPressed.map { Mode.editing }, + input.editingFinishButtonPressed.map { Mode.display } + ) + .map { mode in + switch mode { + case .editing: + return true + case .display: + return false + } + } + + let isEditingMode = Observable + .merge( + initialMode.map({ $0 == .editing }), + buttonPress + ) + .asDriver(onErrorJustReturn: false) + + + let alertDriver = editingRequestResult + .compactMap({ $0.error }) + .map({ error in + // 변경 실패 Alert + return DefaultAlertContentVO( + title: "변경 실패", + message: "변경 싪패 이유" + ) + }) + .asDriver(onErrorJustReturn: .default) + + self.output = .init( + centerName: input.centerName.asDriver(onErrorJustReturn: ""), + centerLocation: input.centerLocation.asDriver(onErrorJustReturn: ""), + centerPhoneNumber: input.centerPhoneNumber.asDriver(onErrorJustReturn: ""), + centerIntroduction: input.centerIntroduction.asDriver(onErrorJustReturn: ""), + centerImage: input.centerImage.asDriver(onErrorJustReturn: UIImage()), + isEditingMode: isEditingMode, + editingValidation: editingValidation, + alert: alertDriver + ) + } + + public func requestData() { + + // 서버로 부터 데이터를 요청하는 API + input.centerName.accept("네 얼간이 방문요양센터") + input.centerLocation.accept("강남구 삼성동 512-3") + input.centerPhoneNumber.accept("(02) 123-4567") + input.centerIntroduction.accept("안녕하세요 반갑습니다!") + input.centerImage.accept(UIImage()) + } +} + + +public extension CenterProfileViewModel { + + class Input: CenterProfileInputable { + + // 서버에서 받아오는데이터 + public var centerName = BehaviorRelay(value: "") + public var centerLocation = BehaviorRelay(value: "") + public var centerPhoneNumber = BehaviorRelay(value: "") + public var centerIntroduction = BehaviorRelay(value: "") + public var centerImage = BehaviorRelay(value: .init()) + + // ViewController에서 받아오는 데이터 + public var editingButtonPressed: PublishRelay = .init() + public var editingFinishButtonPressed: PublishRelay = .init() + public var editingPhoneNumber: BehaviorRelay = .init(value: "") + public var editingInstruction: BehaviorRelay = .init(value: "") + public var editingImage: BehaviorRelay = .init(value: .init()) + } + + class Output: CenterProfileOutputable { + // 기본 데이터 + public var centerName: Driver + public var centerLocation: Driver + public var centerPhoneNumber: Driver + public var centerIntroduction: Driver + public var centerImage: Driver + + // 수정 상태 여부 + public var isEditingMode: Driver + + // 요구사항 X + public var editingValidation: Driver + + public var alert: Driver + + init(centerName: Driver, centerLocation: Driver, centerPhoneNumber: Driver, centerIntroduction: Driver, centerImage: Driver, isEditingMode: Driver, editingValidation: Driver, alert: Driver) { + self.centerName = centerName + self.centerLocation = centerLocation + self.centerPhoneNumber = centerPhoneNumber + self.centerIntroduction = centerIntroduction + self.centerImage = centerImage + self.isEditingMode = isEditingMode + self.editingValidation = editingValidation + self.alert = alert + } + } +} diff --git a/project/Projects/Presentation/PresentationCore/Sources/ViewModelType/Constraint/Auth/RegisterSuccessOutputable.swift b/project/Projects/Presentation/PresentationCore/Sources/ViewModelType/InputOuputConstraint/Auth/RegisterSuccessOutputable.swift similarity index 100% rename from project/Projects/Presentation/PresentationCore/Sources/ViewModelType/Constraint/Auth/RegisterSuccessOutputable.swift rename to project/Projects/Presentation/PresentationCore/Sources/ViewModelType/InputOuputConstraint/Auth/RegisterSuccessOutputable.swift diff --git a/project/Projects/Presentation/PresentationCore/Sources/ViewModelType/Constraint/CTAButton.swift b/project/Projects/Presentation/PresentationCore/Sources/ViewModelType/InputOuputConstraint/CTAButton.swift similarity index 100% rename from project/Projects/Presentation/PresentationCore/Sources/ViewModelType/Constraint/CTAButton.swift rename to project/Projects/Presentation/PresentationCore/Sources/ViewModelType/InputOuputConstraint/CTAButton.swift