From 031f90a7c40add72ff74fd88fe31ef6f89ad246f Mon Sep 17 00:00:00 2001 From: sun Date: Fri, 24 Mar 2023 15:40:28 +0900 Subject: [PATCH 1/5] =?UTF-8?q?[feat]=20=ED=81=AC=EB=9E=98=EC=8B=9C?= =?UTF-8?q?=EB=A1=9C=20=EC=9D=B8=ED=95=B4=20BaseUI=20=EC=9A=94=EC=86=8C?= =?UTF-8?q?=EB=93=A4=20receive(on:)=20=EB=A9=94=EC=84=9C=EB=93=9C=20?= =?UTF-8?q?=EC=A3=BC=EC=84=9D=20=EC=B2=98=EB=A6=AC=20#300?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Base/UI/Controller/CustomNavigationController.swift | 2 +- .../Base/UI/Controller/CustomTabBarController.swift | 2 +- Happiggy-bank/Happiggy-bank/Base/UI/View/BaseButton.swift | 2 +- Happiggy-bank/Happiggy-bank/Base/UI/View/BaseLabel.swift | 2 +- Happiggy-bank/Happiggy-bank/Base/UI/View/BaseTextField.swift | 2 +- Happiggy-bank/Happiggy-bank/Base/UI/View/BaseTextView.swift | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Happiggy-bank/Happiggy-bank/Base/UI/Controller/CustomNavigationController.swift b/Happiggy-bank/Happiggy-bank/Base/UI/Controller/CustomNavigationController.swift index 39e31326..c3abc1b7 100644 --- a/Happiggy-bank/Happiggy-bank/Base/UI/Controller/CustomNavigationController.swift +++ b/Happiggy-bank/Happiggy-bank/Base/UI/Controller/CustomNavigationController.swift @@ -57,7 +57,7 @@ final class CustomNavigationController: UINavigationController { private func subscribeToFontPublisher() { self.cancellable = fontManager.fontPublisher - .receive(on: DispatchQueue.main) +// .receive(on: DispatchQueue.main) .sink { [weak self] in self?.updateFont(to: $0) } } diff --git a/Happiggy-bank/Happiggy-bank/Base/UI/Controller/CustomTabBarController.swift b/Happiggy-bank/Happiggy-bank/Base/UI/Controller/CustomTabBarController.swift index bd81d446..b01b9b79 100644 --- a/Happiggy-bank/Happiggy-bank/Base/UI/Controller/CustomTabBarController.swift +++ b/Happiggy-bank/Happiggy-bank/Base/UI/Controller/CustomTabBarController.swift @@ -77,7 +77,7 @@ final class CustomTabBarController: UITabBarController { private func subscribeToFontPublisher() { self.cancellable = fontManager.fontPublisher - .receive(on: DispatchQueue.main) +// .receive(on: DispatchQueue.main) .sink { [weak self] in self?.updateFont(to: $0) } } diff --git a/Happiggy-bank/Happiggy-bank/Base/UI/View/BaseButton.swift b/Happiggy-bank/Happiggy-bank/Base/UI/View/BaseButton.swift index beb27651..19b0797c 100644 --- a/Happiggy-bank/Happiggy-bank/Base/UI/View/BaseButton.swift +++ b/Happiggy-bank/Happiggy-bank/Base/UI/View/BaseButton.swift @@ -45,7 +45,7 @@ final class BaseButton: UIButton { private func subscribeToFontPublisher() { self.cancellable = fontManager.fontPublisher - .receive(on: DispatchQueue.main) +// .receive(on: DispatchQueue.main) .sink { [weak self] in self?.updateFont(to: $0, isBold: self?.titleLabel?.font.isBold == true) } diff --git a/Happiggy-bank/Happiggy-bank/Base/UI/View/BaseLabel.swift b/Happiggy-bank/Happiggy-bank/Base/UI/View/BaseLabel.swift index 2dddd3f6..8afe534c 100644 --- a/Happiggy-bank/Happiggy-bank/Base/UI/View/BaseLabel.swift +++ b/Happiggy-bank/Happiggy-bank/Base/UI/View/BaseLabel.swift @@ -45,7 +45,7 @@ final class BaseLabel: UILabel { private func subscribeToFontPublisher() { self.cancellable = fontManager.fontPublisher - .receive(on: DispatchQueue.main) +// .receive(on: DispatchQueue.main) .sink { [weak self] in self?.updateFont(to: $0, isBold: self?.font.isBold == true) } } diff --git a/Happiggy-bank/Happiggy-bank/Base/UI/View/BaseTextField.swift b/Happiggy-bank/Happiggy-bank/Base/UI/View/BaseTextField.swift index 198071c3..055fe4d5 100644 --- a/Happiggy-bank/Happiggy-bank/Base/UI/View/BaseTextField.swift +++ b/Happiggy-bank/Happiggy-bank/Base/UI/View/BaseTextField.swift @@ -45,7 +45,7 @@ final class BaseTextField: UITextField { private func subscribeToFontPublisher() { self.cancellable = fontManager.fontPublisher - .receive(on: DispatchQueue.main) +// .receive(on: DispatchQueue.main) .sink { [weak self] in self?.updateFont(to: $0, isBold: self?.font?.isBold == true) } } diff --git a/Happiggy-bank/Happiggy-bank/Base/UI/View/BaseTextView.swift b/Happiggy-bank/Happiggy-bank/Base/UI/View/BaseTextView.swift index f6650642..431b89bf 100644 --- a/Happiggy-bank/Happiggy-bank/Base/UI/View/BaseTextView.swift +++ b/Happiggy-bank/Happiggy-bank/Base/UI/View/BaseTextView.swift @@ -45,7 +45,7 @@ final class BaseTextView: UITextView { private func subscribeToFontPublisher() { self.cancellable = fontManager.fontPublisher - .receive(on: DispatchQueue.main) +// .receive(on: DispatchQueue.main) .sink { [weak self] in self?.updateFont(to: $0, isBold: self?.font?.isBold == true) } } From 534472693acd8991a203daf92762c2c1befb9d16 Mon Sep 17 00:00:00 2001 From: sun Date: Thu, 16 Mar 2023 14:10:09 +0900 Subject: [PATCH 2/5] =?UTF-8?q?[refactor]=20NewNoteInputView=20xib?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=EC=BD=94=EB=93=9C=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20#299?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Happiggy-bank.xcodeproj/project.pbxproj | 4 - .../NewNoteTextViewController.swift | 18 +- .../NewNote/UI/View/NewNoteInputView.swift | 192 +++++++++++++----- .../NewNote/UI/View/NewNoteInputView.xib | 178 ---------------- .../Happiggy-bank/Resource/AssetImage.swift | 2 +- .../Contents.json | 0 .../ico_date.svg | 0 7 files changed, 154 insertions(+), 240 deletions(-) delete mode 100644 Happiggy-bank/Happiggy-bank/HomeTab/NewNote/UI/View/NewNoteInputView.xib rename Happiggy-bank/Happiggy-bank/Resource/Assets.xcassets/images/buttons/newNoteInputView/{date.imageset => calendar.imageset}/Contents.json (100%) rename Happiggy-bank/Happiggy-bank/Resource/Assets.xcassets/images/buttons/newNoteInputView/{date.imageset => calendar.imageset}/ico_date.svg (100%) diff --git a/Happiggy-bank/Happiggy-bank.xcodeproj/project.pbxproj b/Happiggy-bank/Happiggy-bank.xcodeproj/project.pbxproj index 933fb29f..ef892261 100644 --- a/Happiggy-bank/Happiggy-bank.xcodeproj/project.pbxproj +++ b/Happiggy-bank/Happiggy-bank.xcodeproj/project.pbxproj @@ -110,7 +110,6 @@ A49B25F12812BF6800399630 /* dovemayo_bold.otf in Resources */ = {isa = PBXBuildFile; fileRef = A49B25F02812BF4100399630 /* dovemayo_bold.otf */; }; A49B25F22812BF6B00399630 /* dovemayo.otf in Resources */ = {isa = PBXBuildFile; fileRef = A49B25EF2812BF4100399630 /* dovemayo.otf */; }; A49B25F42812FFB400399630 /* UILabel+Color.swift in Sources */ = {isa = PBXBuildFile; fileRef = A49B25F32812FFB400399630 /* UILabel+Color.swift */; }; - A4A55A0528FFC64A004ABE00 /* NewNoteInputView.xib in Resources */ = {isa = PBXBuildFile; fileRef = A4A55A0428FFC64A004ABE00 /* NewNoteInputView.xib */; }; A4A55A0728FFC664004ABE00 /* UIView+ConfigureXib.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4A55A0628FFC664004ABE00 /* UIView+ConfigureXib.swift */; }; A4A55A0928FFC675004ABE00 /* NewNoteInputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4A55A0828FFC675004ABE00 /* NewNoteInputView.swift */; }; A4B285FD27D8A060008769EB /* Calendar+Duration.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4B285FC27D8A060008769EB /* Calendar+Duration.swift */; }; @@ -290,7 +289,6 @@ A49B25EF2812BF4100399630 /* dovemayo.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = dovemayo.otf; sourceTree = ""; }; A49B25F02812BF4100399630 /* dovemayo_bold.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = dovemayo_bold.otf; sourceTree = ""; }; A49B25F32812FFB400399630 /* UILabel+Color.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UILabel+Color.swift"; sourceTree = ""; }; - A4A55A0428FFC64A004ABE00 /* NewNoteInputView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = NewNoteInputView.xib; sourceTree = ""; }; A4A55A0628FFC664004ABE00 /* UIView+ConfigureXib.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIView+ConfigureXib.swift"; sourceTree = ""; }; A4A55A0828FFC675004ABE00 /* NewNoteInputView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NewNoteInputView.swift; sourceTree = ""; }; A4B285FC27D8A060008769EB /* Calendar+Duration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Calendar+Duration.swift"; sourceTree = ""; }; @@ -817,7 +815,6 @@ A896FBEF2966B5DE00A3400B /* View */ = { isa = PBXGroup; children = ( - A4A55A0428FFC64A004ABE00 /* NewNoteInputView.xib */, A467B5C927DA289500AC702D /* NewNoteDatePickerRowView.xib */, A4A55A0828FFC675004ABE00 /* NewNoteInputView.swift */, A467B5C727DA258700AC702D /* NewNoteDatePickerRowView.swift */, @@ -1324,7 +1321,6 @@ A46B110A28146171004AB185 /* GowunBatang-Regular.ttf in Resources */, A49B25F12812BF6800399630 /* dovemayo_bold.otf in Resources */, A46B110D2814617B004AB185 /* IBMPlexSansKR-Regular.otf in Resources */, - A4A55A0528FFC64A004ABE00 /* NewNoteInputView.xib in Resources */, A8FC07DD27B3EF030077A758 /* .swiftlint.yml in Resources */, A467B5CA27DA289600AC702D /* NewNoteDatePickerRowView.xib in Resources */, A46B111128146188004AB185 /* Cafe24SsurroundAir.ttf in Resources */, diff --git a/Happiggy-bank/Happiggy-bank/HomeTab/NewNote/UI/Controller/NewNoteTextViewController.swift b/Happiggy-bank/Happiggy-bank/HomeTab/NewNote/UI/Controller/NewNoteTextViewController.swift index 73815dc1..1518c459 100644 --- a/Happiggy-bank/Happiggy-bank/HomeTab/NewNote/UI/Controller/NewNoteTextViewController.swift +++ b/Happiggy-bank/Happiggy-bank/HomeTab/NewNote/UI/Controller/NewNoteTextViewController.swift @@ -20,7 +20,7 @@ final class NewNoteTextViewController: UIViewController { @IBOutlet weak var saveButton: UIBarButtonItem! /// 쪽지 입력 뷰 - @IBOutlet weak var newNoteInputView: NewNoteInputView! + private var newNoteInputView: NewNoteInputView = NewNoteInputView() /// 쪽지 입력 뷰 높이 제약 조건 @IBOutlet weak var newtNoteInputViewHeightConstraint: NSLayoutConstraint! @@ -71,8 +71,8 @@ final class NewNoteTextViewController: UIViewController { /// 저장버튼(v)을 눌렀을 때 호출되는 액션 메서드 @IBAction func saveButtonDidTap(_ sender: UIBarButtonItem) { - guard let textView = self.newNoteInputView.textView, - !textView.text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty + let textView = self.newNoteInputView.textView + guard !textView.text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else { self.showWarningLabel = true self.newNoteInputView.warningLabel.fadeIn() @@ -111,7 +111,7 @@ final class NewNoteTextViewController: UIViewController { let safeArea = self.view.safeAreaInsets let verticalSafeAreaInsets = safeArea.top + safeArea.bottom let heightThatFits = min( - self.newNoteInputView.contentHeight, + self.newNoteInputView.contentSize.height, self.view.bounds.height - verticalSafeAreaInsets ) self.newtNoteInputViewHeightConstraint.constant = heightThatFits @@ -164,7 +164,7 @@ final class NewNoteTextViewController: UIViewController { ) self?.newNoteInputView.textView.resignFirstResponder() } - self.newNoteInputView.dateButton.addAction(action, for: .touchUpInside) + self.newNoteInputView.calendarButton.addAction(action, for: .touchUpInside) self.updateDateButton() } @@ -177,13 +177,13 @@ final class NewNoteTextViewController: UIViewController { self.view.backgroundColor = backgroundColor self.newNoteInputView.backgroundNoteImageView.tintColor = borderColor - self.newNoteInputView.dateButton.tintColor = tintColor + self.newNoteInputView.calendarButton.tintColor = tintColor self.newNoteInputView.letterCountLabel.textColor = tintColor } /// 쪽지 입력 뷰의 날짜 버튼 제목 업데이트 private func updateDateButton() { - self.newNoteInputView.dateButton.setAttributedTitle( + self.newNoteInputView.calendarButton.setAttributedTitle( self.viewModel.attributedDateButtonTitle, for: .normal ) @@ -407,9 +407,9 @@ extension NewNoteTextViewController: UITextViewDelegate { func textViewDidChange(_ textView: UITextView) { let placeholderLabel = self.newNoteInputView.placeholderLabel if textView.text.isEmpty { - placeholderLabel?.fadeIn() + placeholderLabel.fadeIn() } else { - placeholderLabel?.fadeOut() + placeholderLabel.fadeOut() } self.updateLetterCountLabel(count: textView.text.count) } diff --git a/Happiggy-bank/Happiggy-bank/HomeTab/NewNote/UI/View/NewNoteInputView.swift b/Happiggy-bank/Happiggy-bank/HomeTab/NewNote/UI/View/NewNoteInputView.swift index a11b0574..e20961ff 100644 --- a/Happiggy-bank/Happiggy-bank/HomeTab/NewNote/UI/View/NewNoteInputView.swift +++ b/Happiggy-bank/Happiggy-bank/HomeTab/NewNote/UI/View/NewNoteInputView.swift @@ -7,11 +7,13 @@ import UIKit +import SnapKit +import Then + /// 쪽지 작성 시 나타나는 뷰 -@IBDesignable -final class NewNoteInputView: UIView { +final class NewNoteInputView: UIScrollView { - // MARK: - @IBOulets + // MARK: - Properties var photo: UIImage? { get { self.photoView.image } @@ -22,41 +24,99 @@ final class NewNoteInputView: UIView { } /// 배경이 되는 쪽지 이미지 뷰 - @IBOutlet weak var backgroundNoteImageView: UIImageView! + let backgroundNoteImageView = UIImageView().then { + $0.clipsToBounds = true + $0.isUserInteractionEnabled = true + let inset = UIEdgeInsets( + top: Metric.spacing16, + left: Metric.spacing16, + bottom: Metric.spacing16, + right: Metric.spacing16 + ) + $0.image = AssetImage.noteLine?.resizableImage(withCapInsets: inset, resizingMode: .stretch) + $0.setContentHuggingPriority(.defaultLow, for: .vertical) + } /// 날짜 피커를 띄우는 버튼 - @IBOutlet weak var dateButton: UIButton! - - /// 유저가 선택한 사진을 띄우는 이미지 뷰 - @IBOutlet weak var photoView: UIImageView! + let calendarButton = BaseButton().then { + $0.setImage(AssetImage.calendar, for: .normal) + $0.titleLabel?.changeFontSize(to: FontSize.body3) + $0.adjustsImageWhenHighlighted = false + } /// 유저가 선택한 사진을 제거하는 버튼 - @IBOutlet weak var removePhotoButton: UIButton! + let removePhotoButton = BaseButton().then { + $0.setImage(AssetImage.deleteImage, for: .normal) + } + + /// 쪽지 내용을 작성하는 텍스트 뷰 + let textView = BaseTextView().then { + $0.font = $0.font?.withSize(FontSize.body1) + $0.configureParagraphStyle( + lineSpacing: ParagraphStyle.lineSpacing, + characterSpacing: ParagraphStyle.characterSpacing + ) + $0.backgroundColor = .clear + $0.isScrollEnabled = false + $0.setContentHuggingPriority(.defaultHigh, for: .vertical) + } /// 쪽지 텍스트 뷰의 플레이스 홀더 - @IBOutlet weak var placeholderLabel: UILabel! + let placeholderLabel = BaseLabel().then { + $0.textColor = AssetColor.noteWhiteText + $0.changeFontSize(to: FontSize.body1) + $0.configureParagraphStyle( + lineSpacing: ParagraphStyle.lineSpacing, + characterSpacing: ParagraphStyle.characterSpacing + ) + $0.attributedText = NSAttributedString(string: StringLiteral.placeholder) + } /// 내용이 비었음을 경고하는 레이블 - @IBOutlet weak var warningLabel: UILabel! - - /// 쪽지 내용을 작성하는 텍스트 뷰 - @IBOutlet weak var textView: UITextView! + let warningLabel = BaseLabel().then { + $0.textColor = AssetColor.etcAlert + $0.changeFontSize(to: FontSize.body1) + $0.configureParagraphStyle( + lineSpacing: ParagraphStyle.lineSpacing, + characterSpacing: ParagraphStyle.characterSpacing + ) + $0.attributedText = NSAttributedString(string: StringLiteral.warning) + $0.isHidden = true + } /// 쪽지 글자 수를 나타내는 레이블 - @IBOutlet weak var letterCountLabel: UILabel! + let letterCountLabel = BaseLabel().then { + $0.text = .empty + $0.setContentHuggingPriority(.required, for: .vertical) + $0.sizeToFit() + $0.changeFontSize(to: FontSize.body3) + $0.configureParagraphStyle( + lineSpacing: ParagraphStyle.lineSpacing, + characterSpacing: ParagraphStyle.characterSpacing + ) + $0.textAlignment = .right + } - /// 모든 하위 뷰를 담고 있는 스크롤 뷰 - @IBOutlet weak private var scrollView: UIScrollView! + /// 캘린더 버튼 태그, 텍스트뷰, 글자 수 레이블이 들어가는 스택 뷰 + private let contentStack = UIStackView().then { + $0.axis = .vertical + $0.spacing = Metric.spacing16 + } - /// photoView와 removePhotoButton을 하위 뷰로 갖는 뷰 - @IBOutlet weak var removablePhotoView: UIView! + private let calendarStack = UIStackView() + private let calendarStackSpacer = UIView() - // MARK: Properties + /// photoView와 removePhotoButton을 하위 뷰로 갖는 뷰 + private let removablePhotoView = UIView().then { + $0.isHidden = true + } - /// 내용의 길이(높이) - var contentHeight: CGFloat { - self.scrollView.contentSize.height + /// 유저가 선택한 사진을 띄우는 이미지 뷰 + private let photoView = UIImageView().then { + $0.isUserInteractionEnabled = true + $0.contentMode = .scaleAspectFill + $0.clipsToBounds = true } @@ -65,45 +125,81 @@ final class NewNoteInputView: UIView { override init(frame: CGRect) { super.init(frame: frame) - self.configure() + self.configureViews() } required init?(coder: NSCoder) { super.init(coder: coder) - self.configure() + self.configureViews() } - // MARK: - Funtions - - override func prepareForInterfaceBuilder() { - super.prepareForInterfaceBuilder() - } + // MARK: - Configuration Functions - /// 뷰 초기 설정 - private func configure() { - self.configureXib() - self.configureTextView() - self.configureLabels(self.placeholderLabel, self.warningLabel) - self.photoView.contentMode = .scaleAspectFill + private func configureViews() { + self.showsVerticalScrollIndicator = false + self.showsHorizontalScrollIndicator = false + self.configureSubviews() + self.configureConstraints() } - /// 텍스트 뷰 관련 초기 설정 - private func configureTextView() { - self.textView.configureParagraphStyle( - lineSpacing: ParagraphStyle.lineSpacing, - characterSpacing: ParagraphStyle.characterSpacing + private func configureSubviews() { + self.addSubview(backgroundNoteImageView) + self.backgroundNoteImageView.addSubview(self.contentStack) + self.contentStack.addArrangedSubviews( + self.calendarStack, + self.removablePhotoView, + self.textView, + self.letterCountLabel ) + self.calendarStack.addArrangedSubviews(self.calendarButton, self.calendarStackSpacer) + self.removablePhotoView.addSubviews(self.photoView, self.removePhotoButton) + self.textView.addSubviews(self.placeholderLabel, self.warningLabel) } - /// 플레이스홀더 라벨 초기 설정 - private func configureLabels(_ labels: UILabel...) { - labels.forEach { - $0.configureParagraphStyle( - lineSpacing: ParagraphStyle.lineSpacing, - characterSpacing: ParagraphStyle.characterSpacing - ) + private func configureConstraints() { + self.backgroundNoteImageView.snp.makeConstraints { + $0.edges.equalTo(self.contentLayoutGuide) + $0.width.equalTo(self.frameLayoutGuide) + $0.height.equalTo(self.frameLayoutGuide).priority(.low) } + self.contentStack.snp.makeConstraints { + $0.edges.equalTo(self.backgroundNoteImageView).inset(Metric.spacing24) + } + self.calendarStack.snp.makeConstraints { $0.height.equalTo(Metric.spacing24) } + self.photoView.snp.makeConstraints { + $0.verticalEdges.centerX.equalTo(self.removablePhotoView) + $0.width.equalTo(self.photoView.snp.height).multipliedBy(CGFloat.one) + $0.horizontalEdges.equalTo(self.backgroundNoteImageView).inset(Metric.spacing75) + } + self.removePhotoButton.snp.makeConstraints { + $0.width.height.equalTo(Metric.spacing24) + $0.top.right.equalTo(self.photoView).inset(Metric.spacing10) + } + self.placeholderLabel.snp.makeConstraints { + $0.top.equalTo(self.textView).offset(Metric.spacing9) + $0.leading.equalTo(self.textView).offset(Metric.spacing5) + } + self.warningLabel.snp.makeConstraints { $0.top.leading.equalTo(self.placeholderLabel) } + } +} + + +// MARK: - Constants +fileprivate extension NewNoteInputView { + + enum Metric { + static let spacing5: CGFloat = 5 + static let spacing9: CGFloat = 9 + static let spacing10: CGFloat = 10 + static let spacing16: CGFloat = 16 + static let spacing24: CGFloat = 24 + static let spacing75: CGFloat = 75 + } + + enum StringLiteral { + static let placeholder = "하루 한 번, 100자로 행복을 기록하세요:)" + static let warning = "내용을 입력해주세요!" } } diff --git a/Happiggy-bank/Happiggy-bank/HomeTab/NewNote/UI/View/NewNoteInputView.xib b/Happiggy-bank/Happiggy-bank/HomeTab/NewNote/UI/View/NewNoteInputView.xib deleted file mode 100644 index 51b2461d..00000000 --- a/Happiggy-bank/Happiggy-bank/HomeTab/NewNote/UI/View/NewNoteInputView.xib +++ /dev/null @@ -1,178 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Happiggy-bank/Happiggy-bank/Resource/AssetImage.swift b/Happiggy-bank/Happiggy-bank/Resource/AssetImage.swift index 8f0598d7..4b26440f 100644 --- a/Happiggy-bank/Happiggy-bank/Resource/AssetImage.swift +++ b/Happiggy-bank/Happiggy-bank/Resource/AssetImage.swift @@ -83,7 +83,7 @@ enum AssetImage { // MARK: - Note input view static let gallery = UIImage(named: "gallery") - static let date = UIImage(named: "date") + static let calendar = UIImage(named: "calendar") static let deleteImage = UIImage(named: "deleteImage") diff --git a/Happiggy-bank/Happiggy-bank/Resource/Assets.xcassets/images/buttons/newNoteInputView/date.imageset/Contents.json b/Happiggy-bank/Happiggy-bank/Resource/Assets.xcassets/images/buttons/newNoteInputView/calendar.imageset/Contents.json similarity index 100% rename from Happiggy-bank/Happiggy-bank/Resource/Assets.xcassets/images/buttons/newNoteInputView/date.imageset/Contents.json rename to Happiggy-bank/Happiggy-bank/Resource/Assets.xcassets/images/buttons/newNoteInputView/calendar.imageset/Contents.json diff --git a/Happiggy-bank/Happiggy-bank/Resource/Assets.xcassets/images/buttons/newNoteInputView/date.imageset/ico_date.svg b/Happiggy-bank/Happiggy-bank/Resource/Assets.xcassets/images/buttons/newNoteInputView/calendar.imageset/ico_date.svg similarity index 100% rename from Happiggy-bank/Happiggy-bank/Resource/Assets.xcassets/images/buttons/newNoteInputView/date.imageset/ico_date.svg rename to Happiggy-bank/Happiggy-bank/Resource/Assets.xcassets/images/buttons/newNoteInputView/calendar.imageset/ico_date.svg From 36544d43b4dc7a18eca98096c512eb3240203b34 Mon Sep 17 00:00:00 2001 From: sun Date: Thu, 16 Mar 2023 14:17:19 +0900 Subject: [PATCH 3/5] =?UTF-8?q?[refactor]=20NewNoteInputToolbar=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=20#299?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Happiggy-bank.xcodeproj/project.pbxproj | 4 ++ .../HomeTab/NewNote/UI/View/ColorButton.swift | 10 ++-- .../NewNote/UI/View/NewNoteInputToolbar.swift | 50 +++++++++++++++++++ 3 files changed, 59 insertions(+), 5 deletions(-) create mode 100644 Happiggy-bank/Happiggy-bank/HomeTab/NewNote/UI/View/NewNoteInputToolbar.swift diff --git a/Happiggy-bank/Happiggy-bank.xcodeproj/project.pbxproj b/Happiggy-bank/Happiggy-bank.xcodeproj/project.pbxproj index ef892261..5c14d9ac 100644 --- a/Happiggy-bank/Happiggy-bank.xcodeproj/project.pbxproj +++ b/Happiggy-bank/Happiggy-bank.xcodeproj/project.pbxproj @@ -65,6 +65,7 @@ A46B2E8129B5EBDA006A7870 /* NoteDetailListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A46B2E8029B5EBDA006A7870 /* NoteDetailListViewModel.swift */; }; A46B2E8329B5EBE6006A7870 /* NoteDetailListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A46B2E8229B5EBE6006A7870 /* NoteDetailListViewController.swift */; }; A46BC1EF2800626A00C2E5B4 /* TabItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = A46BC1EE2800626A00C2E5B4 /* TabItem.swift */; }; + A472C5F529C2DCEA00097432 /* NewNoteInputToolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = A472C5F429C2DCEA00097432 /* NewNoteInputToolbar.swift */; }; A47D83462966C0C60028AA1D /* NotificationSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D22ADF7E27F72F7300ECB77B /* NotificationSettingsViewModel.swift */; }; A47D83482966D3870028AA1D /* FontSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A47D83472966D3870028AA1D /* FontSelectionView.swift */; }; A47D834A296716BF0028AA1D /* FontCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = A47D8349296716BF0028AA1D /* FontCell.swift */; }; @@ -245,6 +246,7 @@ A46B2E8029B5EBDA006A7870 /* NoteDetailListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoteDetailListViewModel.swift; sourceTree = ""; }; A46B2E8229B5EBE6006A7870 /* NoteDetailListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoteDetailListViewController.swift; sourceTree = ""; }; A46BC1EE2800626A00C2E5B4 /* TabItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabItem.swift; sourceTree = ""; }; + A472C5F429C2DCEA00097432 /* NewNoteInputToolbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewNoteInputToolbar.swift; sourceTree = ""; }; A47D83472966D3870028AA1D /* FontSelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FontSelectionView.swift; sourceTree = ""; }; A47D8349296716BF0028AA1D /* FontCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FontCell.swift; sourceTree = ""; }; A47D83532973A4860028AA1D /* FontPublishing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FontPublishing.swift; sourceTree = ""; }; @@ -821,6 +823,7 @@ A4CF2C7F27C733FE001B01B1 /* ColorButton.swift */, A4EDFE802902D72A0056C2DC /* ColorPickerBarItem.swift */, A4845B5C2900DF0B00A6007C /* ColorPicker.swift */, + A472C5F429C2DCEA00097432 /* NewNoteInputToolbar.swift */, ); path = View; sourceTree = ""; @@ -1420,6 +1423,7 @@ A4CF2C8227C73B42001B01B1 /* UIColor+AssetColors.swift in Sources */, A819CF9F27DDD97C00DE8E72 /* HapticManager.swift in Sources */, A4569CCF28128B4B001E3FD6 /* SettingsNonIconButtonCell.swift in Sources */, + A472C5F529C2DCEA00097432 /* NewNoteInputToolbar.swift in Sources */, A4396B1B29325D70005D9D3A /* PhotoViewController.swift in Sources */, A8ECD4A727E33BFA00886BC0 /* ListTabViewModel.swift in Sources */, A49AC5EB2917FFAB009315BC /* PhotoNoteCellViewModel.swift in Sources */, diff --git a/Happiggy-bank/Happiggy-bank/HomeTab/NewNote/UI/View/ColorButton.swift b/Happiggy-bank/Happiggy-bank/HomeTab/NewNote/UI/View/ColorButton.swift index 7d7c34e0..52ab2dd9 100644 --- a/Happiggy-bank/Happiggy-bank/HomeTab/NewNote/UI/View/ColorButton.swift +++ b/Happiggy-bank/Happiggy-bank/HomeTab/NewNote/UI/View/ColorButton.swift @@ -20,17 +20,17 @@ final class ColorButton: UIButton { let color: NoteColor /// 버튼의 테두리 색깔 - private let borderColor: UIColor + private let lineColor: UIColor? // MARK: - Init init(color: NoteColor, frame: CGRect = .zero) { self.color = color - self.borderColor = .noteBorder(for: self.color) + self.lineColor = AssetColor.noteLine(for: self.color) super.init(frame: frame) - self.tintColor = .noteHighlight(for: self.color) + self.tintColor = AssetColor.noteText(for: self.color) self.configure() } @@ -60,7 +60,7 @@ final class ColorButton: UIButton { /// 선택되지 않은 경우 하이라이트 효과를 끔 if !self.isSelected { if isLightMode, self.color == .white { - self.layer.borderColor = self.borderColor.cgColor + self.layer.borderColor = self.lineColor?.cgColor return } self.layer.borderWidth = .zero @@ -69,7 +69,7 @@ final class ColorButton: UIButton { /// 뷰 초기화 private func configure() { - self.backgroundColor = .note(color: self.color) + self.backgroundColor = AssetColor.noteBG(for: self.color) self.layer.cornerRadius = Metric.buttonCornerRadius } } diff --git a/Happiggy-bank/Happiggy-bank/HomeTab/NewNote/UI/View/NewNoteInputToolbar.swift b/Happiggy-bank/Happiggy-bank/HomeTab/NewNote/UI/View/NewNoteInputToolbar.swift new file mode 100644 index 00000000..50481cb9 --- /dev/null +++ b/Happiggy-bank/Happiggy-bank/HomeTab/NewNote/UI/View/NewNoteInputToolbar.swift @@ -0,0 +1,50 @@ +// +// NewNoteInputToolbar.swift +// Happiggy-bank +// +// Created by sun on 2023/03/16. +// + +import UIKit + +/// 쪽지 작성뷰에서 사용하는, 사진 라이브러리 버튼과 컬러피커가 담긴 툴바 +final class NewNoteInputToolbar: UIToolbar { + + // MARK: - Properties + + /// 사진 라이브러리를 여는 버튼 + let photoButton = UIButton().then { + $0.tintColor = AssetColor.subBrown02 + $0.setImage(AssetImage.gallery, for: .normal) + $0.adjustsImageWhenHighlighted = false + } + + /// 쪽지 컬러 피커 + let colorPicker = ColorPickerItem() + + + // MARK: - Init(s) + + override init(frame: CGRect) { + super.init(frame: frame) + + self.configureViews() + } + + + required init?(coder: NSCoder) { + super.init(coder: coder) + + self.configureViews() + } + + + // MARK: - Initialization Functions + + private func configureViews() { + self.barTintColor = .systemBackground + let spacer = UIBarButtonItem(systemItem: .flexibleSpace) + let buttonItem = UIBarButtonItem(customView: self.photoButton) + self.setItems([buttonItem, spacer, self.colorPicker], animated: true) + } +} From 799aecee4b7d427a58d9a2487bea50142a46d4a8 Mon Sep 17 00:00:00 2001 From: sun Date: Thu, 16 Mar 2023 15:35:20 +0900 Subject: [PATCH 4/5] =?UTF-8?q?[refactor]=20NewNoteInputViewModel=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=20#299?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Happiggy-bank.xcodeproj/project.pbxproj | 8 ++ .../ViewModel/NewNoteInputViewModel.swift | 117 ++++++++++++++++++ .../Happiggy-bank/Utils/Enum/HBError.swift | 22 ++++ 3 files changed, 147 insertions(+) create mode 100644 Happiggy-bank/Happiggy-bank/HomeTab/NewNote/ViewModel/NewNoteInputViewModel.swift create mode 100644 Happiggy-bank/Happiggy-bank/Utils/Enum/HBError.swift diff --git a/Happiggy-bank/Happiggy-bank.xcodeproj/project.pbxproj b/Happiggy-bank/Happiggy-bank.xcodeproj/project.pbxproj index 5c14d9ac..887c3463 100644 --- a/Happiggy-bank/Happiggy-bank.xcodeproj/project.pbxproj +++ b/Happiggy-bank/Happiggy-bank.xcodeproj/project.pbxproj @@ -66,6 +66,8 @@ A46B2E8329B5EBE6006A7870 /* NoteDetailListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A46B2E8229B5EBE6006A7870 /* NoteDetailListViewController.swift */; }; A46BC1EF2800626A00C2E5B4 /* TabItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = A46BC1EE2800626A00C2E5B4 /* TabItem.swift */; }; A472C5F529C2DCEA00097432 /* NewNoteInputToolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = A472C5F429C2DCEA00097432 /* NewNoteInputToolbar.swift */; }; + A472C5F729C2DDFF00097432 /* NewNoteInputViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A472C5F629C2DDFF00097432 /* NewNoteInputViewModel.swift */; }; + A472C5F929C2ED5000097432 /* HBError.swift in Sources */ = {isa = PBXBuildFile; fileRef = A472C5F829C2ED5000097432 /* HBError.swift */; }; A47D83462966C0C60028AA1D /* NotificationSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D22ADF7E27F72F7300ECB77B /* NotificationSettingsViewModel.swift */; }; A47D83482966D3870028AA1D /* FontSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A47D83472966D3870028AA1D /* FontSelectionView.swift */; }; A47D834A296716BF0028AA1D /* FontCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = A47D8349296716BF0028AA1D /* FontCell.swift */; }; @@ -247,6 +249,8 @@ A46B2E8229B5EBE6006A7870 /* NoteDetailListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoteDetailListViewController.swift; sourceTree = ""; }; A46BC1EE2800626A00C2E5B4 /* TabItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabItem.swift; sourceTree = ""; }; A472C5F429C2DCEA00097432 /* NewNoteInputToolbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewNoteInputToolbar.swift; sourceTree = ""; }; + A472C5F629C2DDFF00097432 /* NewNoteInputViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewNoteInputViewModel.swift; sourceTree = ""; }; + A472C5F829C2ED5000097432 /* HBError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HBError.swift; sourceTree = ""; }; A47D83472966D3870028AA1D /* FontSelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FontSelectionView.swift; sourceTree = ""; }; A47D8349296716BF0028AA1D /* FontCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FontCell.swift; sourceTree = ""; }; A47D83532973A4860028AA1D /* FontPublishing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FontPublishing.swift; sourceTree = ""; }; @@ -548,6 +552,7 @@ A46BC1EE2800626A00C2E5B4 /* TabItem.swift */, A4569CB9280FBA23001E3FD6 /* CustomResult.swift */, A49B25EC2812B5A400399630 /* CustomFont.swift */, + A472C5F829C2ED5000097432 /* HBError.swift */, ); path = Enum; sourceTree = ""; @@ -792,6 +797,7 @@ children = ( A4B2860E27D9F539008769EB /* NewNoteDatePickerViewModel.swift */, A4C1AFD127E5C60E0096CD3E /* NewNoteTextViewModel.swift */, + A472C5F629C2DDFF00097432 /* NewNoteInputViewModel.swift */, ); path = ViewModel; sourceTree = ""; @@ -1381,10 +1387,12 @@ A819CFA127DE034F00DE8E72 /* NewBottle.swift in Sources */, A4569CBC2810455B001E3FD6 /* CustomerServiceViewController.swift in Sources */, A4EDFE812902D72A0056C2DC /* ColorPickerBarItem.swift in Sources */, + A472C5F929C2ED5000097432 /* HBError.swift in Sources */, A49AC5E92917CBFB009315BC /* UIStackView+AddArrangedSubviews.swift in Sources */, A4569CC628111EFA001E3FD6 /* InformationTextViewDataSource.swift in Sources */, A80248252963E58A00F2EA81 /* BottleTitleStack.swift in Sources */, D236DB8627FDC43200D7B8F0 /* NewBottleDatePickerViewModel.swift in Sources */, + A472C5F729C2DDFF00097432 /* NewNoteInputViewModel.swift in Sources */, A47D83562973A4970028AA1D /* FontManager.swift in Sources */, A499318327BF5158009FF5A8 /* BottleNoteView.swift in Sources */, A4CF2C8E27CBA49D001B01B1 /* UIViewController+NotificationCenter.swift in Sources */, diff --git a/Happiggy-bank/Happiggy-bank/HomeTab/NewNote/ViewModel/NewNoteInputViewModel.swift b/Happiggy-bank/Happiggy-bank/HomeTab/NewNote/ViewModel/NewNoteInputViewModel.swift new file mode 100644 index 00000000..7ff32845 --- /dev/null +++ b/Happiggy-bank/Happiggy-bank/HomeTab/NewNote/ViewModel/NewNoteInputViewModel.swift @@ -0,0 +1,117 @@ +// +// NewNoteInputViewModel.swift +// Happiggy-bank +// +// Created by sun on 2023/03/16. +// + +import UIKit + +final class NewNoteInputViewModel { + + // MARK: - Properties + + /// 이미지 처리 객체 + + /// 임시 쪽지 객체 + var newNote: NewNote + + /// 배경 색상 + var backgroundColor: UIColor? { AssetColor.noteBG(for: self.newNote.color) } + + /// 테두리 줄 색상 + var lineColor: UIColor? { AssetColor.noteLine(for: self.newNote.color) } + + /// 글자 색상 + var textColor: UIColor? { AssetColor.noteText(for: self.newNote.color) } + + var yearString: String { self.newNote.date.yearString } + + var dateString: String { self.newNote.date.monthDotDayWithDayOfWeekString } + + private let imageMananger = ImageManager() + + + // MARK: - Inits + + init(date: Date, bottle: Bottle) { + self.newNote = NewNote(date: date, bottle: bottle) + } + + + // MARK: - Functions + + func saveNote(withImage image: UIImage?, text: String) -> Result { + var imageURL: String? + + switch self.saveImageIfNeeded(image) { + case .failure(let error): + return .failure(error) + case .success(let url): + imageURL = url + } + + let note = self.makeNewNote(withText: text, imageURL: imageURL) + + switch PersistenceStore.shared.save() { + case .success: + return .success(note) + case .failure(let error): + PersistenceStore.shared.delete(note) + if let imageURL { + self.deleteImage(withImageURL: imageURL) + } + return .failure(error) + } + } + + /// 이미지가 없는 경우 nil, 있는데 저장에 실패한 경우 에러, 있고 저장에 성공한 경우 경로 리턴 + private func saveImageIfNeeded(_ image: UIImage?) -> Result { + guard let image + else { + return .success(nil) + } + + guard image != .error ?? UIImage(), + let url = self.saveImage(image) + else { + return .failure(HBError.imageSaveFailure) + } + + return .success(url) + } + + /// 새로운 노트 엔티티를 생성 + private func makeNewNote(withText text: String, imageURL: String?) -> Note { + Note.create( + id: self.newNote.id, + date: self.newNote.date, + color: self.newNote.color, + content: text, + imageURL: imageURL, + bottle: self.newNote.bottle + ) + } + + /// 이미지를 저장하고, 성공한 경우 경로 엔드포인트를, 실패한 경우 nil 리턴 + private func saveImage(_ image: UIImage) -> String? { + guard let imageID = newNote.imageID + else { + return nil + } + + return self.imageMananger.saveImage(image, noteID: newNote.id, imageID: imageID) + } + + /// 인자로 주어진 경로에 있는 이미지를 삭제 + /// + /// 삭제에 실패하는 경우 한 번 더 시도하고 리턴 + private func deleteImage(withImageURL imageURL: String) { + guard !self.imageMananger.deleteImage(forNote: newNote.id, imageURL: imageURL) + else { + return + } + + self.imageMananger.deleteImage(forNote: newNote.id, imageURL: imageURL) + } +} diff --git a/Happiggy-bank/Happiggy-bank/Utils/Enum/HBError.swift b/Happiggy-bank/Happiggy-bank/Utils/Enum/HBError.swift new file mode 100644 index 00000000..34976cef --- /dev/null +++ b/Happiggy-bank/Happiggy-bank/Utils/Enum/HBError.swift @@ -0,0 +1,22 @@ +// +// HBError.swift +// Happiggy-bank +// +// Created by sun on 2023/03/16. +// + +import Foundation + +enum HBError: LocalizedError { + case imageSaveFailure + + + // MARK: - Properties + + var errorDescription: String? { + switch self { + case .imageSaveFailure: + return NSLocalizedString("사진 저장에 실패했습니다.", comment: "image save failure") + } + } +} From 5d8024887b132ed8a33e2aaf72f5efef752c652f Mon Sep 17 00:00:00 2001 From: sun Date: Thu, 16 Mar 2023 16:31:08 +0900 Subject: [PATCH 5/5] =?UTF-8?q?[refactor]=20NewNoteInputViewController=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=20=EB=B0=8F=20=EA=B8=B0=EC=A1=B4=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20=EC=A0=9C=EA=B1=B0=20#299?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Happiggy-bank.xcodeproj/project.pbxproj | 16 +- .../UI/Controller/HomeTabViewController.swift | 5 +- .../UI/Controller/HomeViewController.swift | 9 +- .../NewNoteDatePickerViewController.swift | 12 +- .../NewNoteInputViewController.swift | 470 ++++++++++++++++++ .../NewNoteTextViewController.swift | 470 ------------------ .../ViewModel/NewNoteTextViewModel.swift | 106 ---- .../Happiggy-bank/Utils/Constants.swift | 86 ---- .../Protocol/NewNoteSavingDelegate.swift | 15 + 9 files changed, 503 insertions(+), 686 deletions(-) create mode 100644 Happiggy-bank/Happiggy-bank/HomeTab/NewNote/UI/Controller/NewNoteInputViewController.swift delete mode 100644 Happiggy-bank/Happiggy-bank/HomeTab/NewNote/UI/Controller/NewNoteTextViewController.swift delete mode 100644 Happiggy-bank/Happiggy-bank/HomeTab/NewNote/ViewModel/NewNoteTextViewModel.swift create mode 100644 Happiggy-bank/Happiggy-bank/Utils/Protocol/NewNoteSavingDelegate.swift diff --git a/Happiggy-bank/Happiggy-bank.xcodeproj/project.pbxproj b/Happiggy-bank/Happiggy-bank.xcodeproj/project.pbxproj index 887c3463..b15f8db5 100644 --- a/Happiggy-bank/Happiggy-bank.xcodeproj/project.pbxproj +++ b/Happiggy-bank/Happiggy-bank.xcodeproj/project.pbxproj @@ -68,6 +68,7 @@ A472C5F529C2DCEA00097432 /* NewNoteInputToolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = A472C5F429C2DCEA00097432 /* NewNoteInputToolbar.swift */; }; A472C5F729C2DDFF00097432 /* NewNoteInputViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A472C5F629C2DDFF00097432 /* NewNoteInputViewModel.swift */; }; A472C5F929C2ED5000097432 /* HBError.swift in Sources */ = {isa = PBXBuildFile; fileRef = A472C5F829C2ED5000097432 /* HBError.swift */; }; + A472C5FB29C2F21600097432 /* NewNoteSavingDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = A472C5FA29C2F21600097432 /* NewNoteSavingDelegate.swift */; }; A47D83462966C0C60028AA1D /* NotificationSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D22ADF7E27F72F7300ECB77B /* NotificationSettingsViewModel.swift */; }; A47D83482966D3870028AA1D /* FontSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A47D83472966D3870028AA1D /* FontSelectionView.swift */; }; A47D834A296716BF0028AA1D /* FontCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = A47D8349296716BF0028AA1D /* FontCell.swift */; }; @@ -117,7 +118,7 @@ A4A55A0928FFC675004ABE00 /* NewNoteInputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4A55A0828FFC675004ABE00 /* NewNoteInputView.swift */; }; A4B285FD27D8A060008769EB /* Calendar+Duration.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4B285FC27D8A060008769EB /* Calendar+Duration.swift */; }; A4B2860527D9A546008769EB /* NewNoteDatePickerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4B2860427D9A546008769EB /* NewNoteDatePickerViewController.swift */; }; - A4B2860927D9A56A008769EB /* NewNoteTextViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4B2860827D9A56A008769EB /* NewNoteTextViewController.swift */; }; + A4B2860927D9A56A008769EB /* NewNoteInputViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4B2860827D9A56A008769EB /* NewNoteInputViewController.swift */; }; A4B2860B27D9B434008769EB /* UIViewController+FadeInOut.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4B2860A27D9B434008769EB /* UIViewController+FadeInOut.swift */; }; A4B2860F27D9F539008769EB /* NewNoteDatePickerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4B2860E27D9F539008769EB /* NewNoteDatePickerViewModel.swift */; }; A4C1AFC027E477180096CD3E /* NSMutableAttributedString+ColorBold.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4C1AFBF27E477180096CD3E /* NSMutableAttributedString+ColorBold.swift */; }; @@ -125,7 +126,6 @@ A4C1AFC427E47DC50096CD3E /* String+NSMutableAttributedStringify.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4C1AFC327E47DC50096CD3E /* String+NSMutableAttributedStringify.swift */; }; A4C1AFC627E482E10096CD3E /* CGFloat+Values.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4C1AFC527E482E10096CD3E /* CGFloat+Values.swift */; }; A4C1AFCE27E4F8150096CD3E /* UIView+FadeInOut.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4C1AFCD27E4F8150096CD3E /* UIView+FadeInOut.swift */; }; - A4C1AFD227E5C60E0096CD3E /* NewNoteTextViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4C1AFD127E5C60E0096CD3E /* NewNoteTextViewModel.swift */; }; A4C1AFD427E5E6120096CD3E /* UITextView+ParagraphStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4C1AFD327E5E6120096CD3E /* UITextView+ParagraphStyle.swift */; }; A4CF2C7C27C71FF5001B01B1 /* CATransition+PopupAnimation.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4CF2C7B27C71FF5001B01B1 /* CATransition+PopupAnimation.swift */; }; A4CF2C8027C733FE001B01B1 /* ColorButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4CF2C7F27C733FE001B01B1 /* ColorButton.swift */; }; @@ -251,6 +251,7 @@ A472C5F429C2DCEA00097432 /* NewNoteInputToolbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewNoteInputToolbar.swift; sourceTree = ""; }; A472C5F629C2DDFF00097432 /* NewNoteInputViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewNoteInputViewModel.swift; sourceTree = ""; }; A472C5F829C2ED5000097432 /* HBError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HBError.swift; sourceTree = ""; }; + A472C5FA29C2F21600097432 /* NewNoteSavingDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewNoteSavingDelegate.swift; sourceTree = ""; }; A47D83472966D3870028AA1D /* FontSelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FontSelectionView.swift; sourceTree = ""; }; A47D8349296716BF0028AA1D /* FontCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FontCell.swift; sourceTree = ""; }; A47D83532973A4860028AA1D /* FontPublishing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FontPublishing.swift; sourceTree = ""; }; @@ -299,7 +300,7 @@ A4A55A0828FFC675004ABE00 /* NewNoteInputView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NewNoteInputView.swift; sourceTree = ""; }; A4B285FC27D8A060008769EB /* Calendar+Duration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Calendar+Duration.swift"; sourceTree = ""; }; A4B2860427D9A546008769EB /* NewNoteDatePickerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewNoteDatePickerViewController.swift; sourceTree = ""; }; - A4B2860827D9A56A008769EB /* NewNoteTextViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewNoteTextViewController.swift; sourceTree = ""; }; + A4B2860827D9A56A008769EB /* NewNoteInputViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewNoteInputViewController.swift; sourceTree = ""; }; A4B2860A27D9B434008769EB /* UIViewController+FadeInOut.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+FadeInOut.swift"; sourceTree = ""; }; A4B2860E27D9F539008769EB /* NewNoteDatePickerViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewNoteDatePickerViewModel.swift; sourceTree = ""; }; A4C1AFBF27E477180096CD3E /* NSMutableAttributedString+ColorBold.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSMutableAttributedString+ColorBold.swift"; sourceTree = ""; }; @@ -307,7 +308,6 @@ A4C1AFC327E47DC50096CD3E /* String+NSMutableAttributedStringify.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+NSMutableAttributedStringify.swift"; sourceTree = ""; }; A4C1AFC527E482E10096CD3E /* CGFloat+Values.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CGFloat+Values.swift"; sourceTree = ""; }; A4C1AFCD27E4F8150096CD3E /* UIView+FadeInOut.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+FadeInOut.swift"; sourceTree = ""; }; - A4C1AFD127E5C60E0096CD3E /* NewNoteTextViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewNoteTextViewModel.swift; sourceTree = ""; }; A4C1AFD327E5E6120096CD3E /* UITextView+ParagraphStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UITextView+ParagraphStyle.swift"; sourceTree = ""; }; A4CF2C7B27C71FF5001B01B1 /* CATransition+PopupAnimation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CATransition+PopupAnimation.swift"; sourceTree = ""; }; A4CF2C7F27C733FE001B01B1 /* ColorButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ColorButton.swift; path = "Happiggy-bank/HomeTab/NewNote/UI/View/ColorButton.swift"; sourceTree = SOURCE_ROOT; }; @@ -574,6 +574,7 @@ A4569CB7280FB979001E3FD6 /* Presenter.swift */, A4569CC528111EFA001E3FD6 /* InformationTextViewDataSource.swift */, A4EDFE7E2902D5CB0056C2DC /* ColorPickerDelegate.swift */, + A472C5FA29C2F21600097432 /* NewNoteSavingDelegate.swift */, ); path = Protocol; sourceTree = ""; @@ -796,7 +797,6 @@ isa = PBXGroup; children = ( A4B2860E27D9F539008769EB /* NewNoteDatePickerViewModel.swift */, - A4C1AFD127E5C60E0096CD3E /* NewNoteTextViewModel.swift */, A472C5F629C2DDFF00097432 /* NewNoteInputViewModel.swift */, ); path = ViewModel; @@ -815,7 +815,7 @@ isa = PBXGroup; children = ( A4B2860427D9A546008769EB /* NewNoteDatePickerViewController.swift */, - A4B2860827D9A56A008769EB /* NewNoteTextViewController.swift */, + A4B2860827D9A56A008769EB /* NewNoteInputViewController.swift */, ); path = Controller; sourceTree = ""; @@ -1383,7 +1383,6 @@ A467B5C827DA258700AC702D /* NewNoteDatePickerRowView.swift in Sources */, A484A3052958945E00A58312 /* BaseTextView.swift in Sources */, A484A327295EE22000A58312 /* String+SubstringsMatchingRegex.swift in Sources */, - A4C1AFD227E5C60E0096CD3E /* NewNoteTextViewModel.swift in Sources */, A819CFA127DE034F00DE8E72 /* NewBottle.swift in Sources */, A4569CBC2810455B001E3FD6 /* CustomerServiceViewController.swift in Sources */, A4EDFE812902D72A0056C2DC /* ColorPickerBarItem.swift in Sources */, @@ -1450,7 +1449,7 @@ A49AC5E52917C6E2009315BC /* UILabel+ChangeFontSize.swift in Sources */, A4A55A0928FFC675004ABE00 /* NewNoteInputView.swift in Sources */, A46B2E8329B5EBE6006A7870 /* NoteDetailListViewController.swift in Sources */, - A4B2860927D9A56A008769EB /* NewNoteTextViewController.swift in Sources */, + A4B2860927D9A56A008769EB /* NewNoteInputViewController.swift in Sources */, A843332127DA026D00A12A54 /* NewBottleDatePickerViewController.swift in Sources */, A4D6EB75282E2E6700553E43 /* VersionChecking.swift in Sources */, A8FC07CA27B3ECF00077A758 /* SceneDelegate.swift in Sources */, @@ -1458,6 +1457,7 @@ A466A31028018CD800D655F4 /* UIWindowScene+TopMostViewController.swift in Sources */, A4569CC428111E1D001E3FD6 /* LicenseViewModel.swift in Sources */, A466A30E28018BBD00D655F4 /* UIVIewController+TopMostViewController.swift in Sources */, + A472C5FB29C2F21600097432 /* NewNoteSavingDelegate.swift in Sources */, D2C48C0127E9DFA1006FC59E /* NoteView.swift in Sources */, A49B25E72812AC2800399630 /* FontSelectionViewController.swift in Sources */, A456657E27CC77A9007CF70A /* Date+Formatted.swift in Sources */, diff --git a/Happiggy-bank/Happiggy-bank/HomeTab/Home/UI/Controller/HomeTabViewController.swift b/Happiggy-bank/Happiggy-bank/HomeTab/Home/UI/Controller/HomeTabViewController.swift index b09ae705..7b55fa7c 100644 --- a/Happiggy-bank/Happiggy-bank/HomeTab/Home/UI/Controller/HomeTabViewController.swift +++ b/Happiggy-bank/Happiggy-bank/HomeTab/Home/UI/Controller/HomeTabViewController.swift @@ -92,9 +92,10 @@ final class HomeTabViewController: UIViewController { } if bottle.isEmtpyToday { - // NewNoteTextViewController + // NewNoteInputViewController + let viewModel = NewNoteInputViewModel(date: Date(), bottle: bottle) self.navigationController?.pushViewControllerWithFade( - to: UIViewController().then { $0.view.backgroundColor = .gray } + to: NewNoteInputViewController(viewModel: viewModel) ) } else { // NewNoteDatePickerViewController diff --git a/Happiggy-bank/Happiggy-bank/HomeTab/Home/UI/Controller/HomeViewController.swift b/Happiggy-bank/Happiggy-bank/HomeTab/Home/UI/Controller/HomeViewController.swift index c255c464..c16d3b4f 100644 --- a/Happiggy-bank/Happiggy-bank/HomeTab/Home/UI/Controller/HomeViewController.swift +++ b/Happiggy-bank/Happiggy-bank/HomeTab/Home/UI/Controller/HomeViewController.swift @@ -189,14 +189,7 @@ final class HomeViewController: UIViewController { let viewModel = NewNoteDatePickerViewModel(initialDate: Date(), bottle: bottle) dateViewController.viewModel = viewModel } - if segue.identifier == SegueIdentifier.presentNewNoteTextView { - guard let textViewController = segue.destination as? NewNoteTextViewController, - let bottle = self.viewModel.bottle - else { return } - - let viewModel = NewNoteTextViewModel(date: Date(), bottle: bottle) - textViewController.viewModel = viewModel - } + if segue.identifier == SegueIdentifier.presentBottleMessageView { guard let bottleMessageController = segue.destination as? BottleMessageViewController, let (fakeBackground, bottle) = sender as? (UIView, Bottle) diff --git a/Happiggy-bank/Happiggy-bank/HomeTab/NewNote/UI/Controller/NewNoteDatePickerViewController.swift b/Happiggy-bank/Happiggy-bank/HomeTab/NewNote/UI/Controller/NewNoteDatePickerViewController.swift index 22defff1..a940b848 100644 --- a/Happiggy-bank/Happiggy-bank/HomeTab/NewNote/UI/Controller/NewNoteDatePickerViewController.swift +++ b/Happiggy-bank/Happiggy-bank/HomeTab/NewNote/UI/Controller/NewNoteDatePickerViewController.swift @@ -232,12 +232,12 @@ final class NewNoteDatePickerViewController: UIViewController { override func prepare(for segue: UIStoryboardSegue, sender: Any?) { if segue.identifier == SegueIdentifier.presentNewNoteTextViewFromDatePicker { - - guard let textViewController = segue.destination as? NewNoteTextViewController - else { return } - - let viewModel = NewNoteTextViewModel(date: self.viewModel.selectedDate, bottle: self.viewModel.bottle) - textViewController.viewModel = viewModel +// +// guard let textViewController = segue.destination as? NewNoteTextViewController +// else { return } +// +// let viewModel = NewNoteTextViewModel(date: self.viewModel.selectedDate, bottle: self.viewModel.bottle) +// textViewController.viewModel = viewModel } } } diff --git a/Happiggy-bank/Happiggy-bank/HomeTab/NewNote/UI/Controller/NewNoteInputViewController.swift b/Happiggy-bank/Happiggy-bank/HomeTab/NewNote/UI/Controller/NewNoteInputViewController.swift new file mode 100644 index 00000000..0cea86c5 --- /dev/null +++ b/Happiggy-bank/Happiggy-bank/HomeTab/NewNote/UI/Controller/NewNoteInputViewController.swift @@ -0,0 +1,470 @@ +// +// NewNoteInputViewController.swift +// Happiggy-bank +// +// Created by sun on 2022/03/10. +// + +import PhotosUI +import UIKit + +import SnapKit +import Then + +/// 새로운 쪽지를 추가할 때 사용하는 뷰 컨트롤러 +/// 쪽지 추가 시 이를 알리기 위해 델리게이트 설정 필요 +final class NewNoteInputViewController: UIViewController { + + // MARK: - Properties + + weak var delegate: NewNoteSavingDelegate? + private let viewModel: NewNoteInputViewModel + private let noteInputView = NewNoteInputView() + /// 에러 로그 방지를 위해 임의의 초기값 설정 + private let toolbar = NewNoteInputToolbar(frame: .init(origin: .zero, size: .init(width: 1000, height: 50))) + private var showWarningLabel = false + private var noteInputViewBotttomConstraint: Constraint? + private var toolbarBottomConstraint: Constraint? + + + // MARK: - Init(s) + + init(viewModel: NewNoteInputViewModel) { + self.viewModel = viewModel + super.init(nibName: nil, bundle: nil) + + self.hidesBottomBarWhenPushed = true + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + + // MARK: - Life Cycle + + override func viewDidLoad() { + super.viewDidLoad() + + self.configureNavigationBar() + self.configureViews() + self.configureToolbar() + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + self.noteInputView.textView.becomeFirstResponder() + self.updateCalendarButtonTitleText() + } + + + // MARK: - NavigationBar Configuration Functions + + private func configureNavigationBar() { + let cancelButton = UIBarButtonItem( + image: AssetImage.xmark, + primaryAction: .init { [weak self] _ in + self?.noteInputView.textView.endEditing(true) + self?.navigationController?.popToRootViewControllerWithFade() + } + ) + let saveButton = UIBarButtonItem( + image: AssetImage.checkmark, + primaryAction: .init { [weak self] _ in self?.saveButtonDidTap() } + ) + self.navigationItem.setLeftBarButton(cancelButton, animated: true) + self.navigationItem.setRightBarButton(saveButton, animated: true) + + self.navigationController?.navigationBar.standardAppearance.backgroundColor = .none + } + + private func saveButtonDidTap() { + let textView = self.noteInputView.textView + guard !textView.text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty + else { + HapticManager.instance.notification(type: .error) + self.showWarningLabel = true + self.noteInputView.warningLabel.fadeIn() + self.noteInputView.placeholderLabel.fadeOut() + return + } + + textView.endEditing(true) + self.present(makeConfirmationAlert(), animated: true) + } + + private func makeConfirmationAlert() -> UIAlertController { + let confirmAction = UIAlertAction.confirmAction(title: StringLiteral.confirmButtonTitle) { [weak self] _ in + guard let text = self?.noteInputView.textView.text, + let saveStatus = self?.viewModel.saveNote(withImage: self?.noteInputView.photo, text: text) + else { + return + } + + switch saveStatus { + case .success(let note): + self?.delegate?.newNoteDidSave(note) + self?.navigationController?.popToRootViewControllerWithFade() + case .failure(let error): + self?.presentSaveFailureAlert(withDescription: error.localizedDescription) + } + } + let cancelAction = UIAlertAction.cancelAction { [weak self] _ in + self?.noteInputView.textView.becomeFirstResponder() + } + + return .basic( + alertTitle: StringLiteral.saveConfirmationAlertTitle, + alertMessage: StringLiteral.saveConfirmationAlertMessage, + confirmAction: confirmAction, + cancelAction: cancelAction + ) + } + + private func presentSaveFailureAlert(withDescription description: String) { + let alert = UIAlertController.basic( + alertTitle: StringLiteral.saveFailureAlertTItle, + alertMessage: description, + preferredStyle: .alert, + confirmAction: .confirmAction() + ) + self.present(alert, animated: true) + } + + + // MARK: - View Configuration Functions + + private func configureViews() { + self.configureSubviews() + self.configureConstraints() + self.observeKeyboardAppearnce() + } + + private func configureSubviews() { + self.view.addSubview(self.noteInputView) + self.noteInputView.backgroundNoteImageView.addGestureRecognizer(UITapGestureRecognizer( + target: self, + action: #selector(backgroundDidTap(_:)) + )) + self.updateColor() + self.configureTextView() + self.configureCalendarButton() + self.updateLetterCountLabel(count: .zero) + self.noteInputView.removePhotoButton.addAction(UIAction(handler: { [weak self] _ in + self?.noteInputView.photo = nil + self?.viewModel.newNote.imageID = nil + self?.toolbar.photoButton.isEnabled = true + }), for: .touchUpInside) + } + + @objc private func backgroundDidTap(_ sender: UITapGestureRecognizer) { + self.noteInputView.textView.resignFirstResponder() + + self.toolbarBottomConstraint?.update(inset: Int.zero) + let toolbarHeight = self.toolbar.frame.height + + self.noteInputViewBotttomConstraint?.deactivate() + self.noteInputView.snp.makeConstraints { + self.noteInputViewBotttomConstraint = $0.bottom.equalTo(self.view.safeAreaLayoutGuide) + .inset(toolbarHeight) + .priority(.high) + .constraint + } + } + + private func updateColor() { + self.view.backgroundColor = self.viewModel.backgroundColor + self.noteInputView.backgroundNoteImageView.tintColor = self.viewModel.lineColor + self.noteInputView.calendarButton.tintColor = self.viewModel.textColor + self.noteInputView.letterCountLabel.textColor = self.viewModel.textColor + self.updateCalendarButtonTitleText() + } + + private func configureCalendarButton() { + let action = UIAction { [weak self] _ in + print("move to date select view controller ") + self?.navigationController?.pushViewControllerWithFade(to: UIViewController()) + self?.noteInputView.textView.resignFirstResponder() + } + self.noteInputView.calendarButton.addAction(action, for: .touchUpInside) + self.updateCalendarButtonTitleText() + } + + private func updateCalendarButtonTitleText() { + let calendarButton = self.noteInputView.calendarButton + let customFont = (calendarButton.customFont ?? .current) + let fontSize = calendarButton.titleLabel?.font.pointSize ?? FontSize.body3 + let font = UIFont(name: customFont.regular, size: fontSize) ?? .systemFont(ofSize: fontSize) + let boldFont = UIFont(name: customFont.bold, size: fontSize) ?? .boldSystemFont(ofSize: fontSize) + let title = " \(viewModel.yearString) \(viewModel.dateString)" + .nsMutableAttributedStringify() + .color(color: viewModel.textColor ?? .black) + .font(font) + .bold(font: boldFont, targetString: viewModel.yearString) + + self.noteInputView.calendarButton.setAttributedTitle(title, for: .normal) + } + + private func configureTextView() { + self.noteInputView.textView.delegate = self + self.noteInputView.textView.becomeFirstResponder() + } + + private func updateLetterCountLabel(count: Int) { + let label = self.noteInputView.letterCountLabel + let customFont = (label.customFont ?? .current) + let fontSize = label.font.pointSize + let font = UIFont(name: customFont.regular, size: fontSize) ?? .systemFont(ofSize: fontSize) + let boldFont = UIFont(name: customFont.bold, size: fontSize) ?? .boldSystemFont(ofSize: fontSize) + let isLongerThanLimit = self.noteInputView.textView.text.count > Metric.noteTextMaxLength + let countColor = isLongerThanLimit ? AssetColor.etcAlert : self.viewModel.textColor + + let title = "\(count) / \(Metric.noteTextMaxLength)" + .nsMutableAttributedStringify() + .color(color: self.viewModel.textColor ?? .black) + .color(targetString: count.description, color: countColor ?? .black) + .font(font) + .bold(font: boldFont, targetString: count.description) + + self.noteInputView.letterCountLabel.attributedText = title + } + + func attributedLetterCountString(count: Int) -> NSMutableAttributedString { + let label = self.noteInputView.letterCountLabel + let customFont = (label.customFont ?? .current) + let fontSize = label.font.pointSize + let font = UIFont(name: customFont.regular, size: fontSize) ?? .systemFont(ofSize: fontSize) + let boldFont = UIFont(name: customFont.bold, size: fontSize) ?? .boldSystemFont(ofSize: fontSize) + let color = viewModel.textColor ?? .black + + return "\(count) / \(Metric.noteTextMaxLength)" + .nsMutableAttributedStringify() + .color(color: color) + .font(font) + .bold(font: boldFont, targetString: count.description) + } + + private func configureConstraints() { + self.noteInputView.snp.makeConstraints { + $0.top.horizontalEdges.equalTo(self.view.safeAreaLayoutGuide) + self.noteInputViewBotttomConstraint = $0.bottom.equalTo(self.view.safeAreaLayoutGuide).constraint + } + } + + private func observeKeyboardAppearnce() { + NotificationCenter.default.addObserver( + forName: UIResponder.keyboardWillShowNotification, + object: nil, + queue: nil, + using: self.updateRelatedConstraints(notification:) + ) + } + + /// 키보드에 맞춰 noteInputView의 길이와 toolbar의 위치 변경 + private func updateRelatedConstraints(notification: Notification) { + guard let info = notification.userInfo, + let keyboardFrame = info[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect + else { return } + + let keyboardHeight = keyboardFrame.height - self.view.safeAreaInsets.bottom + self.toolbarBottomConstraint?.update(inset: keyboardHeight) + + let toolbarHeight = self.toolbar.frame.height + self.noteInputViewBotttomConstraint?.deactivate() + self.noteInputView.snp.makeConstraints { + self.noteInputViewBotttomConstraint = $0.bottom.equalTo(self.view.safeAreaLayoutGuide) + .inset(keyboardHeight + toolbarHeight) + .constraint + } + } + + + // MARK: - Toolbar Configuration Functions + + private func configureToolbar() { + self.view.addSubview(self.toolbar) + self.toolbar.snp.makeConstraints { + $0.horizontalEdges.equalTo(self.view.safeAreaLayoutGuide) + self.toolbarBottomConstraint = $0.bottom.equalTo(self.view.safeAreaLayoutGuide).constraint + } + self.toolbar.photoButton.addAction(UIAction { [weak self] _ in + self?.presentPhotoPicker() }, for: .touchUpInside) + self.toolbar.colorPicker.delegate = self + } + + private func presentPhotoPicker() { + var configuration = PHPickerConfiguration(photoLibrary: .shared()) + configuration.filter = .images + + let picker = PHPickerViewController(configuration: configuration) + picker.delegate = self + self.noteInputView.textView.resignFirstResponder() + self.present(picker, animated: true) + } +} + + +// MARK: - UITextViewDelegate +extension NewNoteInputViewController: UITextViewDelegate { + + func textViewDidEndEditing(_ textView: UITextView) { + + /// 100자 초과 시 초과분 삭제 + if textView.text.count > Metric.noteTextMaxLength { + textView.text = String(textView.text.prefix(Metric.noteTextMaxLength)) + self.updateLetterCountLabel(count: textView.text.count) + } + + /// 키보드 아래로 내리는 애니메이션 + textView.resignFirstResponder() + } + + func textView( + _ textView: UITextView, + shouldChangeTextIn range: NSRange, + replacementText text: String + ) -> Bool { + + /// 텍스트가 유효한지, 편집한 범위를 찾을 수 있는 지 확인 + guard let currentText = textView.text, + Range(range, in: currentText) != nil + else { return false } + + /// 경고 라벨이 나와 있으면 숨김처리 + if self.showWarningLabel { + self.showWarningLabel = false + self.noteInputView.warningLabel.fadeOut() + } + + let updatedTextLength = textView.text.count - range.length + text.count + let trimLength = updatedTextLength - Metric.krOverflowCap + + guard updatedTextLength > Metric.krOverflowCap, + text.count >= trimLength + else { + /// 내용이 빈 상태에서 백스페이스를 누르는 경우 + if textView.text.isEmpty, text.isEmpty { + self.noteInputView.placeholderLabel.fadeIn() + } + return true + } + + /// 새로 입력된 문자열의 초과분을 삭제 + let index = text.index(text.endIndex, offsetBy: -trimLength) + let trimmedReplacementText = text[.. Note { - Note.create( - id: self.viewModel.newNote.id, - date: self.viewModel.newNote.date, - color: self.viewModel.newNote.color, - content: self.newNoteInputView.textView.text, - imageURL: imageURL, - bottle: self.viewModel.newNote.bottle - ) - } - - /// 새로 생성한 노트 엔티티를 저장하고 성공 여부에 따라 불 리턴 - private func saveAndPostNewNote() -> Bool { - guard let (errorTitle, errorMessage) = PersistenceStore.shared.saveOld() - else { return true } - - let alert = PersistenceStore.shared.makeErrorAlert( - title: errorTitle, - message: errorMessage - ) { [weak self] _ in - self?.dismissWithAnimation() - } - self.present(alert, animated: true) - - return false - } - - /// 쪽지 저장 의사를 재확인 하는 알림을 띄움 - private func showNoteSavingConfirmationAlert() { - let alert = self.makeConfirmationAlert() - self.present(alert, animated: true) - } - - /// 쪽지 저장 의사를 재확인하는 알림 생성 - private func makeConfirmationAlert() -> UIAlertController { - let confirmAction = UIAlertAction.confirmAction( - title: StringLiteral.confirmButtonTitle - ) { [weak self] _ in - - var imageURL = String?.none - - if let image = self?.newNoteInputView.photo, - image != (.error ?? UIImage()) { - guard let url = self?.viewModel.saveImage(image) - else { - let alert = UIAlertController.basic( - alertTitle: "저장에 실패했습니다.", - preferredStyle: .alert, - confirmAction: .confirmAction() - ) - self?.present(alert, animated: true) - return - } - imageURL = url - } - - guard let note = self?.makeNewNote(withImageURL: imageURL) - else { - return - } - - guard self?.saveAndPostNewNote() == true - else { - PersistenceStore.shared.delete(note) - if let imageURL = note.imageURL { - self?.viewModel.deleteImage(withImageURL: imageURL) - } - return - } - - let noteAndDelay = (note: note, delay: CATransition.transitionDuration) - self?.post(name: .noteDidAdd, object: noteAndDelay) - self?.dismissWithAnimation() - } - - let cancelAction = UIAlertAction.cancelAction { [weak self] _ in - self?.newNoteInputView.textView.becomeFirstResponder() - } - - return UIAlertController.basic( - alertTitle: StringLiteral.alertTitle, - alertMessage: StringLiteral.message, - confirmAction: confirmAction, - cancelAction: cancelAction - ) - } - - /// 페이드아웃 효과와 함께 종료 - private func dismissWithAnimation() { - self.fadeOut() - self.performSegue( - withIdentifier: SegueIdentifier.unwindFromNoteTextViewToHomeView, - sender: self - ) - } - - - // MARK: - Photo Selecting Functions - - private func presentPhotoPicker() { - var configuration = PHPickerConfiguration(photoLibrary: .shared()) - configuration.filter = .images - - let picker = PHPickerViewController(configuration: configuration) - picker.delegate = self - self.newNoteInputView.textView.resignFirstResponder() - self.present(picker, animated: true) - } - - - - // MARK: - Navigation - - override func prepare(for segue: UIStoryboardSegue, sender: Any?) { - if segue.identifier == SegueIdentifier.presentDatePickerFromNoteTextView { - guard let dateViewController = segue.destination as? NewNoteDatePickerViewController - else { return } - - let viewModel = NewNoteDatePickerViewModel( - initialDate: self.viewModel.newNote.date, - bottle: self.viewModel.newNote.bottle - ) - - dateViewController.viewModel = viewModel - dateViewController.isFromNoteTextView = true - } - } -} - - -// MARK: - UITextViewDelegate - -extension NewNoteTextViewController: UITextViewDelegate { - - func textViewDidEndEditing(_ textView: UITextView) { - - /// 100자 초과 시 초과분 삭제 - if textView.text.count > Metric.noteTextMaxLength { - textView.text = String(textView.text.prefix(Metric.noteTextMaxLength)) - self.updateLetterCountLabel(count: textView.text.count) - } - - /// 키보드 아래로 내리는 애니메이션 - textView.resignFirstResponder() - } - - func textView( - _ textView: UITextView, - shouldChangeTextIn range: NSRange, - replacementText text: String - ) -> Bool { - - /// 텍스트가 유효한지, 편집한 범위를 찾을 수 있는 지 확인 - guard let currentText = textView.text, - Range(range, in: currentText) != nil - else { return false } - - /// 경고 라벨이 나와 있으면 숨김처리 - if self.showWarningLabel { - self.showWarningLabel = false - self.newNoteInputView.warningLabel.fadeOut() - } - - var overflowCap = Metric.noteTextMaxLength - // if textView.textInputMode?.primaryLanguage == StringLiteral.korean { - // /// 한글의 경우 초성, 중성, 종성으로 이루어져 있어서 100자를 제대로 받기 위해 제한을 1글자 키움 - // overflowCap = Metric.krOverflowCap - // } - overflowCap = Metric.krOverflowCap - - let updatedTextLength = textView.text.count - range.length + text.count - let trimLength = updatedTextLength - overflowCap - - guard updatedTextLength > overflowCap, - text.count >= trimLength - else { - /// 내용이 빈 상태에서 백스페이스를 누르는 경우 - if textView.text.isEmpty, text.isEmpty { - self.newNoteInputView.placeholderLabel.fadeIn() - } - return true - } - - /// 새로 입력된 문자열의 초과분을 삭제 - let index = text.index(text.endIndex, offsetBy: -trimLength) - let trimmedReplacementText = text[.. NSMutableAttributedString { - let color = (count > NewNoteTextViewController.Metric.noteTextMaxLength) ? - UIColor.customWarningLabel : self.tintColor - - let countString = "\(count)" - .nsMutableAttributedStringify() - .bold() - .color(color: color) - - countString.append(StringLiteral.letterCountText.nsMutableAttributedStringify()) - - return countString - } - - /// 이미지를 저장하고, 성공한 경우 경로 엔드포인트를, 실패한 경우 nil 리턴 - func saveImage(_ image: UIImage) -> String? { - guard let imageID = newNote.imageID - else { - return nil - } - - return self.imageMananger.saveImage(image, noteID: newNote.id, imageID: imageID) - } - - /// 인자로 주어진 경로에 있는 이미지를 삭제 - /// - /// 삭제에 실패하는 경우 한 번 더 시도하고 리턴 - func deleteImage(withImageURL imageURL: String) { - guard !self.imageMananger.deleteImage(forNote: newNote.id, imageURL: imageURL) - else { - return - } - - self.imageMananger.deleteImage(forNote: newNote.id, imageURL: imageURL) - } -} diff --git a/Happiggy-bank/Happiggy-bank/Utils/Constants.swift b/Happiggy-bank/Happiggy-bank/Utils/Constants.swift index d1b254a9..6a88ba99 100644 --- a/Happiggy-bank/Happiggy-bank/Utils/Constants.swift +++ b/Happiggy-bank/Happiggy-bank/Utils/Constants.swift @@ -412,72 +412,6 @@ enum Asset: String { case settings } -extension NewNoteTextViewController { - - /// NewNoteTextViewController에서 사용하는 상수값 - enum Metric { - - /// 텍스트 뷰 컨테이너 인셋: (위: 16, 왼쪽: 24, 아래: 24, 오른쪽: 24) - static let textViewInset = UIEdgeInsets( - top: 16, - left: 24, - bottom: 24, - right: 24 - ) - - /// note 의 최대 작성 가능 길이 : 100 자 - static let noteTextMaxLength = 100 - - /// 한국 글자수 제한을 위한 오버플로우 cap 추가 값: 1 - static let krOverflowCap = noteTextMaxLength + 1 - - /// 애니메이션 지속 시간: 0.2 - static let animationDuration = CATransition.transitionDuration - - /// 내용 스택이 내비게이션바, safe area top inset, 키보드 크기를 제외한 나머지 영역을 다 차지할 수 있도록 높이를 계산해서 리턴 - static func contentStackHeight( - keyboardFrame: CGRect, - navigationBarFrame: CGRect - ) -> CGFloat { - let keyboardHeight = keyboardFrame.size.height - let navigationBarHeight = navigationBarFrame.size.height - let screenHeight = UIScreen.main.bounds.height - let safeAreaTopInset = navigationBarFrame.origin.y - let removingHeight = safeAreaTopInset + navigationBarHeight + keyboardHeight - - return screenHeight - removingHeight - } - - /// 컬러 버튼 컨테이너 뷰 페이드 인 지속 시간: 0.1 - static let colorButtonContainerViewFadeInDuration: TimeInterval = 0.1 - - /// 컬러 버튼 컨테이너 뷰 페이드 아웃 지속 시간: 0.1 - static let colorButtonContainerViewFadeOutDuration: TimeInterval = 0.1 - } - - /// NewNoteTextViewController 에서 설정하는 제목들 - enum StringLiteral { - - /// 키보드 언어 설정이 한글인 경우 - static let korean = "ko-KR" - - /// 저장 확인 알림 제목 - static let alertTitle = "쪽지를 추가하시겠어요?" - - /// 알림 내용 - static let message = """ -쪽지는 하루에 한 번 작성할 수 있고, -추가 후에는 수정/삭제가 불가능합니다 -""" - - /// 취소 버튼 제목: "취소" - static let cancelButtonTitle = "취소" - - /// 확인 버튼 제목: "추가" - static let confirmButtonTitle = "추가" - } -} - extension NewNoteDatePickerViewModel { /// NoteNoteDatePickerViewModel 에서 지정하는 폰트 크기 @@ -488,26 +422,6 @@ extension NewNoteDatePickerViewModel { } } -extension NewNoteTextViewModel { - - /// NewNoteTextViewModel 에서 사용하는 문자열 - enum StringLiteral { - - /// 글자수 라벨 텍스트를 반환 - static let letterCountText = " / \(NewNoteTextViewController.Metric.noteTextMaxLength)" - - /// 날짜 레이블 간격 - static let spacing = " " - } - - /// NewNoteTextViewModel 에서 사용하는 폰트 크기 - enum Font { - - /// 날짜 버튼과 글자수 라벨 폰트 크기: 15 - static let secondaryText: CGFloat = 15 - } -} - extension SettingsViewCell { /// 상수값 diff --git a/Happiggy-bank/Happiggy-bank/Utils/Protocol/NewNoteSavingDelegate.swift b/Happiggy-bank/Happiggy-bank/Utils/Protocol/NewNoteSavingDelegate.swift new file mode 100644 index 00000000..b7b3e0fe --- /dev/null +++ b/Happiggy-bank/Happiggy-bank/Utils/Protocol/NewNoteSavingDelegate.swift @@ -0,0 +1,15 @@ +// +// NewNoteSavingDelegate.swift +// Happiggy-bank +// +// Created by sun on 2023/03/16. +// + +import Foundation + +/// 새로운 쪽지를 추가했을 때 이를 알림받아야 하는 클래스가 채택하는 프로토콜 +protocol NewNoteSavingDelegate: AnyObject { + + /// 새로운 쪽지가 추가되었음을 전달 + func newNoteDidSave(_ note: Note) +}