diff --git a/Diary+CoreDataClass.swift b/Diary+CoreDataClass.swift new file mode 100644 index 000000000..1b6d38998 --- /dev/null +++ b/Diary+CoreDataClass.swift @@ -0,0 +1,15 @@ +// +// Diary+CoreDataClass.swift +// Diary +// +// Created by Mary & Whales on 2023/09/05. +// +// + +import Foundation +import CoreData + +@objc(Diary) +public class Diary: NSManagedObject { + +} diff --git a/Diary+CoreDataProperties.swift b/Diary+CoreDataProperties.swift new file mode 100644 index 000000000..4217e98d7 --- /dev/null +++ b/Diary+CoreDataProperties.swift @@ -0,0 +1,27 @@ +// +// Diary+CoreDataProperties.swift +// Diary +// +// Created by Mary & Whales on 2023/09/05. +// +// + +import Foundation +import CoreData + +extension Diary { + + @nonobjc public class func fetchRequest() -> NSFetchRequest { + return NSFetchRequest(entityName: "Diary") + } + + @NSManaged public var title: String + @NSManaged public var body: String + @NSManaged public var timeInterval: Double + @NSManaged public var id: UUID + +} + +extension Diary: Identifiable { + +} diff --git a/Diary.xcodeproj/project.pbxproj b/Diary.xcodeproj/project.pbxproj index 330407466..d16749696 100644 --- a/Diary.xcodeproj/project.pbxproj +++ b/Diary.xcodeproj/project.pbxproj @@ -11,8 +11,10 @@ 3C3C08902AA3AB6C00C8D4CF /* UIViewController+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C3C088F2AA3AB6C00C8D4CF /* UIViewController+.swift */; }; 3C3C08EE2AA6055500C8D4CF /* Collection+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C3C08ED2AA6055500C8D4CF /* Collection+.swift */; }; 3C3C08F02AA6241D00C8D4CF /* DateFormatter+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C3C08EF2AA6241D00C8D4CF /* DateFormatter+.swift */; }; + 3C6B0B772AB427C30011E59D /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C6B0B762AB427C30011E59D /* Logger.swift */; }; 3CCA1E772A9DBF56008683C3 /* .swiftlint.yml in Resources */ = {isa = PBXBuildFile; fileRef = 3CCA1E762A9DBF56008683C3 /* .swiftlint.yml */; }; 3CCA1E792A9F08C8008683C3 /* DiaryContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CCA1E782A9F08C8008683C3 /* DiaryContent.swift */; }; + 444E73F82AAAFA940079BEE5 /* ActivityViewPresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 444E73F72AAAFA940079BEE5 /* ActivityViewPresentable.swift */; }; 4495F2EF2A9CCF62007D5278 /* DiaryTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4495F2EE2A9CCF62007D5278 /* DiaryTableViewCell.swift */; }; 44A761BA2A9F100900C10AE2 /* DecodingManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44A761B92A9F100900C10AE2 /* DecodingManager.swift */; }; 44A761BD2A9F142B00C10AE2 /* DataError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44A761BC2A9F142B00C10AE2 /* DataError.swift */; }; @@ -20,6 +22,10 @@ 44A761C12A9F7A6100C10AE2 /* EditingDiaryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44A761C02A9F7A6100C10AE2 /* EditingDiaryViewController.swift */; }; 44A762012AA5F96700C10AE2 /* UITableViewCell+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44A762002AA5F96700C10AE2 /* UITableViewCell+.swift */; }; 44A762032AA6031800C10AE2 /* ReuseIdentifiable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44A762022AA6031800C10AE2 /* ReuseIdentifiable.swift */; }; + 44A7622D2AA710F000C10AE2 /* Diary+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44A7622B2AA710F000C10AE2 /* Diary+CoreDataClass.swift */; }; + 44A7622E2AA710F000C10AE2 /* Diary+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44A7622C2AA710F000C10AE2 /* Diary+CoreDataProperties.swift */; }; + 44DC00332AB36F6000AF3C0E /* String+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44DC00322AB36F6000AF3C0E /* String+.swift */; }; + 44DC00572AB630E900AF3C0E /* DiaryEditable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44DC00562AB630E900AF3C0E /* DiaryEditable.swift */; }; C739AE25284DF28600741E8F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C739AE24284DF28600741E8F /* AppDelegate.swift */; }; C739AE27284DF28600741E8F /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C739AE26284DF28600741E8F /* SceneDelegate.swift */; }; C739AE29284DF28600741E8F /* DiaryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C739AE28284DF28600741E8F /* DiaryViewController.swift */; }; @@ -34,8 +40,10 @@ 3C3C088F2AA3AB6C00C8D4CF /* UIViewController+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+.swift"; sourceTree = ""; }; 3C3C08ED2AA6055500C8D4CF /* Collection+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Collection+.swift"; sourceTree = ""; }; 3C3C08EF2AA6241D00C8D4CF /* DateFormatter+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DateFormatter+.swift"; sourceTree = ""; }; + 3C6B0B762AB427C30011E59D /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = ""; }; 3CCA1E762A9DBF56008683C3 /* .swiftlint.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = .swiftlint.yml; sourceTree = ""; }; 3CCA1E782A9F08C8008683C3 /* DiaryContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiaryContent.swift; sourceTree = ""; }; + 444E73F72AAAFA940079BEE5 /* ActivityViewPresentable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityViewPresentable.swift; sourceTree = ""; }; 4495F2EE2A9CCF62007D5278 /* DiaryTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiaryTableViewCell.swift; sourceTree = ""; }; 44A761B92A9F100900C10AE2 /* DecodingManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DecodingManager.swift; sourceTree = ""; }; 44A761BC2A9F142B00C10AE2 /* DataError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataError.swift; sourceTree = ""; }; @@ -43,6 +51,10 @@ 44A761C02A9F7A6100C10AE2 /* EditingDiaryViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditingDiaryViewController.swift; sourceTree = ""; }; 44A762002AA5F96700C10AE2 /* UITableViewCell+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UITableViewCell+.swift"; sourceTree = ""; }; 44A762022AA6031800C10AE2 /* ReuseIdentifiable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReuseIdentifiable.swift; sourceTree = ""; }; + 44A7622B2AA710F000C10AE2 /* Diary+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Diary+CoreDataClass.swift"; sourceTree = ""; }; + 44A7622C2AA710F000C10AE2 /* Diary+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Diary+CoreDataProperties.swift"; sourceTree = ""; }; + 44DC00322AB36F6000AF3C0E /* String+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+.swift"; sourceTree = ""; }; + 44DC00562AB630E900AF3C0E /* DiaryEditable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiaryEditable.swift; sourceTree = ""; }; C739AE21284DF28600741E8F /* Diary.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Diary.app; sourceTree = BUILT_PRODUCTS_DIR; }; C739AE24284DF28600741E8F /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; C739AE26284DF28600741E8F /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; @@ -71,6 +83,7 @@ 3C3C08EF2AA6241D00C8D4CF /* DateFormatter+.swift */, 3C3C088F2AA3AB6C00C8D4CF /* UIViewController+.swift */, 44A762002AA5F96700C10AE2 /* UITableViewCell+.swift */, + 44DC00322AB36F6000AF3C0E /* String+.swift */, ); path = Extension; sourceTree = ""; @@ -89,6 +102,9 @@ children = ( 44A761B92A9F100900C10AE2 /* DecodingManager.swift */, 44A762022AA6031800C10AE2 /* ReuseIdentifiable.swift */, + 444E73F72AAAFA940079BEE5 /* ActivityViewPresentable.swift */, + 3C6B0B762AB427C30011E59D /* Logger.swift */, + 44DC00562AB630E900AF3C0E /* DiaryEditable.swift */, ); path = Utility; sourceTree = ""; @@ -141,6 +157,8 @@ C739AE18284DF28600741E8F = { isa = PBXGroup; children = ( + 44A7622B2AA710F000C10AE2 /* Diary+CoreDataClass.swift */, + 44A7622C2AA710F000C10AE2 /* Diary+CoreDataProperties.swift */, 3C3C086B2AA1DD8D00C8D4CF /* Localizable.strings */, 3CCA1E762A9DBF56008683C3 /* .swiftlint.yml */, C739AE23284DF28600741E8F /* Diary */, @@ -268,7 +286,10 @@ buildActionMask = 2147483647; files = ( 44A761BA2A9F100900C10AE2 /* DecodingManager.swift in Sources */, + 44A7622D2AA710F000C10AE2 /* Diary+CoreDataClass.swift in Sources */, + 44DC00332AB36F6000AF3C0E /* String+.swift in Sources */, 44A762032AA6031800C10AE2 /* ReuseIdentifiable.swift in Sources */, + 3C6B0B772AB427C30011E59D /* Logger.swift in Sources */, C739AE29284DF28600741E8F /* DiaryViewController.swift in Sources */, 4495F2EF2A9CCF62007D5278 /* DiaryTableViewCell.swift in Sources */, C739AE25284DF28600741E8F /* AppDelegate.swift in Sources */, @@ -277,8 +298,11 @@ C739AE27284DF28600741E8F /* SceneDelegate.swift in Sources */, 44A761BD2A9F142B00C10AE2 /* DataError.swift in Sources */, 44A762012AA5F96700C10AE2 /* UITableViewCell+.swift in Sources */, + 444E73F82AAAFA940079BEE5 /* ActivityViewPresentable.swift in Sources */, 3C3C08902AA3AB6C00C8D4CF /* UIViewController+.swift in Sources */, + 44A7622E2AA710F000C10AE2 /* Diary+CoreDataProperties.swift in Sources */, 3C3C08F02AA6241D00C8D4CF /* DateFormatter+.swift in Sources */, + 44DC00572AB630E900AF3C0E /* DiaryEditable.swift in Sources */, C739AE2F284DF28600741E8F /* Diary.xcdatamodeld in Sources */, 44A761C12A9F7A6100C10AE2 /* EditingDiaryViewController.swift in Sources */, 3CCA1E792A9F08C8008683C3 /* DiaryContent.swift in Sources */, diff --git a/Diary/Application/SceneDelegate.swift b/Diary/Application/SceneDelegate.swift index 549950be2..48d188ab7 100644 --- a/Diary/Application/SceneDelegate.swift +++ b/Diary/Application/SceneDelegate.swift @@ -13,8 +13,9 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { guard let windowScene = (scene as? UIWindowScene) else { return } + guard let diaryManager = try? DiaryManager() else { return } - let rootViewController = DiaryViewController() + let rootViewController = DiaryViewController(diaryManager: diaryManager) let navigationController = UINavigationController(rootViewController: rootViewController) window = UIWindow(windowScene: windowScene) diff --git a/Diary/Controller/DiaryViewController.swift b/Diary/Controller/DiaryViewController.swift index 001a51178..74de8fb95 100644 --- a/Diary/Controller/DiaryViewController.swift +++ b/Diary/Controller/DiaryViewController.swift @@ -7,17 +7,19 @@ import UIKit final class DiaryViewController: UIViewController { - private var diaryManager: DiaryManager + private let diaryManager: DiaryEditable + private let logger: Logger - private var tableView: UITableView = { + private let tableView: UITableView = { let tableView = UITableView() tableView.translatesAutoresizingMaskIntoConstraints = false return tableView }() - init(diaryManager: DiaryManager = DiaryManager()) { + init(diaryManager: DiaryEditable, logger: Logger = Logger()) { self.diaryManager = diaryManager + self.logger = logger super.init(nibName: nil, bundle: nil) } @@ -28,19 +30,24 @@ final class DiaryViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() - - configureView() + + configureUI() configureTableView() - setUpConstraints() + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) - fetchDiaryContents() + refreshDiaries() + tableView.reloadData() } - private func configureView() { + private func configureUI() { view.backgroundColor = .systemBackground view.addSubview(tableView) configureNavigationItem() + setupConstraints() } private func configureNavigationItem() { @@ -49,7 +56,7 @@ final class DiaryViewController: UIViewController { action: #selector(tappedAddDiaryButton)) navigationItem.rightBarButtonItem = addDiaryBarButtonItem - navigationItem.title = String(localized: "title") + navigationItem.title = "title".localized } @objc private func tappedAddDiaryButton() { @@ -59,7 +66,9 @@ final class DiaryViewController: UIViewController { } private func showEditingDiaryViewController(with diaryContent: DiaryContent) { - let editingDiaryViewController = EditingDiaryViewController(with: diaryContent) + let editingDiaryViewController = EditingDiaryViewController(diaryManager: diaryManager, + logger: logger, + with: diaryContent) show(editingDiaryViewController, sender: self) } @@ -70,7 +79,7 @@ final class DiaryViewController: UIViewController { tableView.register(DiaryTableViewCell.self, forCellReuseIdentifier: DiaryTableViewCell.reuseIdentifier) } - private func setUpConstraints() { + private func setupConstraints() { NSLayoutConstraint.activate([ tableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), tableView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor), @@ -78,20 +87,11 @@ final class DiaryViewController: UIViewController { tableView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor), ]) } - - private func fetchDiaryContents() { - do { - try diaryManager.fetchDiaryContents(name: "sample") - } catch { - print(error.localizedDescription) - presentAlertWith(title: "데이터 불러오기 실패", message: "앱을 다시 실행해주십시오.", actionConfigs: ("확인", .default, nil)) - } - } } extension DiaryViewController: UITableViewDataSource { func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return diaryManager.diaryContents?.count ?? .zero + return diaryManager.diaryContents.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { @@ -103,8 +103,7 @@ extension DiaryViewController: UITableViewDataSource { return UITableViewCell() } - guard let diaryContents = diaryManager.diaryContents, - let diaryContent = diaryContents[safe: indexPath.row] + guard let diaryContent = diaryManager.diaryContents[safe: indexPath.row] else { return UITableViewCell() } @@ -115,10 +114,9 @@ extension DiaryViewController: UITableViewDataSource { } } -extension DiaryViewController: UITableViewDelegate { +extension DiaryViewController: UITableViewDelegate, ActivityViewPresentable { func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - guard let diaryContents = diaryManager.diaryContents, - let diaryContent = diaryContents[safe: indexPath.row] + guard let diaryContent = diaryManager.diaryContents[safe: indexPath.row] else { return } @@ -126,4 +124,64 @@ extension DiaryViewController: UITableViewDelegate { showEditingDiaryViewController(with: diaryContent) tableView.deselectRow(at: indexPath, animated: true) } + + func tableView(_ tableView: UITableView, + trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { + guard let diaryContent = diaryManager.diaryContents[safe: indexPath.row] + else { + return nil + } + + let share = UIContextualAction( + style: .normal, + title: "share".localized + ) { [weak self] (_, _, success: @escaping (Bool) -> Void) in + + let diaryContentItem = diaryContent.title + diaryContent.body + + self?.presentActivityView(shareItem: diaryContentItem) + success(true) + } + + let delete = UIContextualAction( + style: .destructive, + title: "delete".localized + ) { [weak self] (_, _, success: @escaping (Bool) -> Void) in + + self?.presentCheckDeleteAlert { [weak self] _ in + self?.deleteDiary(id: diaryContent.id) + tableView.deleteRows(at: [indexPath], with: .fade) + } + + success(true) + } + + return UISwipeActionsConfiguration(actions: [delete, share]) + } +} + +extension DiaryViewController { + private func refreshDiaries() { + do { + try diaryManager.refresh() + } catch { + logger.osLog(error.localizedDescription) + presentAlert(title: "failedFetchDataAlertTitle".localized, + message: "failedFetchDataAlertMessage".localized, + preferredStyle: .alert, + actionConfigs: ("failedFetchDataAlertAction".localized, .default, nil)) + } + } + + private func deleteDiary(id: UUID) { + do { + try diaryManager.delete(id: id) + } catch { + logger.osLog(error.localizedDescription) + presentAlert(title: "failedDeleteDataAlertTitle".localized, + message: "failedDeleteDataAlertMessage".localized, + preferredStyle: .alert, + actionConfigs: ("failedDeleteDataAlertAction".localized, .default, nil)) + } + } } diff --git a/Diary/Controller/EditingDiaryViewController.swift b/Diary/Controller/EditingDiaryViewController.swift index 6a5e85aed..d2085317d 100644 --- a/Diary/Controller/EditingDiaryViewController.swift +++ b/Diary/Controller/EditingDiaryViewController.swift @@ -7,7 +7,9 @@ import UIKit -final class EditingDiaryViewController: UIViewController { +final class EditingDiaryViewController: UIViewController, ActivityViewPresentable { + private let diaryManager: DiaryEditable + private let logger: Logger private var diaryContent: DiaryContent private let diaryTextView: UITextView = { @@ -19,7 +21,9 @@ final class EditingDiaryViewController: UIViewController { return textView }() - init(with diaryContent: DiaryContent) { + init(diaryManager: DiaryEditable, logger: Logger, with diaryContent: DiaryContent) { + self.diaryManager = diaryManager + self.logger = logger self.diaryContent = diaryContent super.init(nibName: nil, bundle: nil) @@ -31,24 +35,63 @@ final class EditingDiaryViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() - - configureView() - setUpConstraints() - fillDiaryTextView() + + configureUI() + setupDiaryTextView() + setObserver() } - private func configureView() { + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + + guard diaryTextView.text.isEmpty else { + save() + return + } + + deleteDiary(id: diaryContent.id) + } + + private func configureUI() { view.backgroundColor = .systemBackground view.addSubview(diaryTextView) configureNavigationItem() + setupConstraints() } private func configureNavigationItem() { + let othersDiaryBarButtonItem = UIBarButtonItem(image: UIImage(systemName: "ellipsis.bubble"), + style: .plain, + target: self, + action: #selector(tappedOthersButton)) + + navigationItem.rightBarButtonItem = othersDiaryBarButtonItem navigationItem.title = diaryContent.date } - private func setUpConstraints() { + @objc private func tappedOthersButton() { + let shareHandler: (UIAlertAction) -> Void = { _ in + let diaryContentItem = self.diaryContent.title + self.diaryContent.body + self.presentActivityView(shareItem: diaryContentItem) + } + + let deleteHandler: (UIAlertAction) -> Void = { _ in + self.presentCheckDeleteAlert { [self] _ in + deleteDiary(id: diaryContent.id) + self.navigationController?.popViewController(animated: true) + } + } + + presentAlert(title: nil, + message: nil, + preferredStyle: .actionSheet, + actionConfigs: ("share".localized, .default, shareHandler), + ("delete".localized, .destructive, deleteHandler), + ("cancel".localized, .cancel, nil)) + } + + private func setupConstraints() { NSLayoutConstraint.activate([ diaryTextView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), diaryTextView.bottomAnchor.constraint(equalTo: view.keyboardLayoutGuide.topAnchor), @@ -57,9 +100,96 @@ final class EditingDiaryViewController: UIViewController { ]) } - private func fillDiaryTextView() { - if diaryContent.title.isEmpty == false { - diaryTextView.text = String(format: "%@\n\n%@", diaryContent.title, diaryContent.body) + private func setupDiaryTextView() { + if diaryContent.title.isEmpty == false || diaryContent.body.isEmpty == false { + diaryTextView.text = diaryContent.title + diaryContent.body + } else { + diaryTextView.becomeFirstResponder() + insertDiary(diaryContent: diaryContent) + } + + addGesture() + diaryTextView.delegate = self + } + + private func addGesture() { + let tapGesture = UITapGestureRecognizer(target: self, action: #selector(tappedTextView(_:))) + diaryTextView.addGestureRecognizer(tapGesture) + } + + @objc private func tappedTextView(_ sender: Any) { + if diaryTextView.isFirstResponder { + diaryTextView.resignFirstResponder() + } else { + diaryTextView.becomeFirstResponder() + } + } + + private func setObserver() { + NotificationCenter.default.addObserver(self, + selector: #selector(enterBackground), + name: UIWindowScene.didEnterBackgroundNotification, + object: nil) + } + + @objc private func enterBackground() { + save() + } + + private func save() { + guard let text = diaryTextView.text else { return } + + if let index = text.firstIndex(of: "\n") { + diaryContent.title = String(text[text.startIndex ..< index]) + diaryContent.body = String(text[index ..< text.endIndex]) + } else { + diaryContent.title = text + } + + updateDiary(diaryContent: diaryContent) + } +} + +extension EditingDiaryViewController: UITextViewDelegate { + func textViewDidEndEditing(_ textView: UITextView) { + save() + } +} + +extension EditingDiaryViewController { + private func deleteDiary(id: UUID) { + do { + try diaryManager.delete(id: id) + } catch { + logger.osLog(error.localizedDescription) + presentAlert(title: "failedDeleteDataAlertTitle".localized, + message: "failedDeleteDataAlertMessage".localized, + preferredStyle: .alert, + actionConfigs: ("failedDeleteDataAlertAction".localized, .default, nil)) + } + } + + private func insertDiary(diaryContent: DiaryContent) { + do { + try diaryManager.insert(diaryContent: diaryContent) + } catch { + logger.osLog(error.localizedDescription) + presentAlert(title: "failedSaveDataAlertTitle".localized, + message: "failedSaveDataAlertMessage".localized, + preferredStyle: .alert, + actionConfigs: ("failedSaveDataAlertAction".localized, .default, nil)) + } + } + + private func updateDiary(diaryContent: DiaryContent) { + do { + try diaryManager.update(diaryContent: diaryContent) + } catch { + logger.osLog(error.localizedDescription) + presentAlert(title: "failedSaveDataAlertTitle".localized, + message: "failedSaveDataAlertMessage".localized, + preferredStyle: .alert, + actionConfigs: ("failedSaveDataAlertAction".localized, .default, nil)) } } } diff --git a/Diary/Extension/String+.swift b/Diary/Extension/String+.swift new file mode 100644 index 000000000..f668c0ac4 --- /dev/null +++ b/Diary/Extension/String+.swift @@ -0,0 +1,12 @@ +// +// String+.swift +// Diary +// +// Created by Mary & Whales on 2023/09/15. +// + +extension String { + var localized: String { + return String(localized: LocalizationValue(self)) + } +} diff --git a/Diary/Extension/UIViewController+.swift b/Diary/Extension/UIViewController+.swift index d6c8f61df..0a1b72dd1 100644 --- a/Diary/Extension/UIViewController+.swift +++ b/Diary/Extension/UIViewController+.swift @@ -8,21 +8,31 @@ import UIKit extension UIViewController { - func presentAlertWith(title: String?, - message: String?, - actionConfigs: (title: String?, - style: UIAlertAction.Style, - handler: ((UIAlertAction) -> Void)?)...) { + func presentAlert(title: String?, + message: String?, + preferredStyle: UIAlertController.Style, + actionConfigs: (title: String?, + style: UIAlertAction.Style, + handler: ((UIAlertAction) -> Void)?)...) { let alertController = UIAlertController(title: title, message: message, - preferredStyle: .alert) + preferredStyle: preferredStyle) + for config in actionConfigs { let action = UIAlertAction(title: config.title, style: config.style, handler: config.handler) alertController.addAction(action) } - + present(alertController, animated: true) } + + func presentCheckDeleteAlert(deleteHandler: @escaping (UIAlertAction) -> Void) { + presentAlert(title: "checkDeleteAlertTitle".localized, + message: "checkDeleteAlertMessage".localized, + preferredStyle: .alert, + actionConfigs: ("checkDeleteAlertCancelAction".localized, .cancel, nil), + ("checkDeleteAlertAction".localized, .destructive, deleteHandler)) + } } diff --git a/Diary/Model/DiaryContent.swift b/Diary/Model/DiaryContent.swift index 0fc49c32e..ee5a9f9d9 100644 --- a/Diary/Model/DiaryContent.swift +++ b/Diary/Model/DiaryContent.swift @@ -7,7 +7,8 @@ import Foundation -struct DiaryContent: Decodable { +struct DiaryContent { + let id: UUID var title: String var body: String let timeInterval: Double @@ -18,8 +19,10 @@ struct DiaryContent: Decodable { return formattedDate } - private enum CodingKeys: String, CodingKey { - case title, body - case timeInterval = "created_at" + init(id: UUID = UUID(), title: String, body: String, timeInterval: Double) { + self.id = id + self.title = title + self.body = body + self.timeInterval = timeInterval } } diff --git a/Diary/Model/DiaryManager.swift b/Diary/Model/DiaryManager.swift index 3c1321e6a..ab5afa110 100644 --- a/Diary/Model/DiaryManager.swift +++ b/Diary/Model/DiaryManager.swift @@ -5,14 +5,86 @@ // Created by Mary & Whales on 2023/08/30. // -import OSLog +import CoreData -struct DiaryManager { - var diaryContents: [DiaryContent]? +final class DiaryManager: DiaryEditable { + private(set) var diaryContents: [DiaryContent] = [] - mutating func fetchDiaryContents(name: String) throws { - let data: [DiaryContent] = try DecodingManager.decodeJSON(fileName: name, by: JSONDecoder()) + private let persistentContainer: NSPersistentContainer = { + let container = NSPersistentContainer(name: "Diary") + container.loadPersistentStores { _, error in + if let error = error { + fatalError("Unable to load persistent stores: \(error)") + } + } - diaryContents = data + return container + }() + + private var context: NSManagedObjectContext { + return persistentContainer.viewContext + } + + init() throws { + try refresh() + } + + func refresh() throws { + let diaries = try context.fetch(Diary.fetchRequest()) + var contents: [DiaryContent] = [] + + diaries.forEach { element in + contents.append(DiaryContent(id: element.id, + title: element.title, + body: element.body, + timeInterval: element.timeInterval)) + } + + diaryContents = contents + } + + func insert(diaryContent: DiaryContent) throws { + let entity = NSEntityDescription.entity(forEntityName: "Diary", in: context) + + if let entity = entity { + let managedObject = NSManagedObject(entity: entity, insertInto: context) + + managedObject.setValue(diaryContent.title, forKey: "title") + managedObject.setValue(diaryContent.body, forKey: "body") + managedObject.setValue(diaryContent.timeInterval, forKey: "timeInterval") + managedObject.setValue(diaryContent.id, forKey: "id") + + try context.save() + } + } + + func update(diaryContent: DiaryContent) throws { + let request = Diary.fetchRequest() + + request.predicate = NSPredicate(format: "id == %@", diaryContent.id as CVarArg) + + let diaries = try context.fetch(request) + + diaries.forEach { diary in + diary.title = diaryContent.title + diary.body = diaryContent.body + } + + try context.save() + } + + func delete(id: UUID) throws { + let request = Diary.fetchRequest() + + request.predicate = NSPredicate(format: "id == %@", id as CVarArg) + + let diaries = try context.fetch(request) + + guard let diary = diaries.first else { return } + + context.delete(diary) + try context.save() + + diaryContents = diaryContents.filter { $0.id != id } } } diff --git a/Diary/Resource/Assets.xcassets/sample.dataset/Contents.json b/Diary/Resource/Assets.xcassets/sample.dataset/Contents.json deleted file mode 100644 index 4a27eac56..000000000 --- a/Diary/Resource/Assets.xcassets/sample.dataset/Contents.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "data" : [ - { - "filename" : "sample.json", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Diary/Resource/Assets.xcassets/sample.dataset/sample.json b/Diary/Resource/Assets.xcassets/sample.dataset/sample.json deleted file mode 100644 index 6746d8848..000000000 --- a/Diary/Resource/Assets.xcassets/sample.dataset/sample.json +++ /dev/null @@ -1,77 +0,0 @@ -[ - { - "title": "똘기떵이호치새초미자축인묘", - "body": "A wonderful serenity has taken possession of my entire soul, like these sweet mornings of spring which I enjoy with my whole heart. I am alone, and feel the charm of existence in this spot, which was created for the bliss of souls like mine.\n\nI am so happy, my dear friend, so absorbed in the exquisite sense of mere tranquil existence, that I neglect my talents. I should be incapable of drawing a single stroke at the present moment; and yet I feel that I never was a greater artist than now.\n\nWhen, while the lovely valley teems with vapour around me, and the meridian sun strikes the upper surface of the impenetrable foliage of my trees, and but a few stray gleams steal into the inner sanctuary, I throw myself down among the tall grass by the trickling stream; and, as I lie close to the earth, a thousand unknown plants are noticed by me: when I hear the buzz of the little world among the stalks, and grow familiar with the countless indescribable forms of the insects and flies, then I feel the presence of the Almighty, who formed us in his own image, and the breath", - "created_at": 1608651333 - }, - { - "title": "드라고요롱이마초미미진사오미", - "body": "A wonderful serenity has taken possession of my entire soul, like these sweet mornings of spring which I enjoy with my whole heart. I am alone, and feel the charm of existence in this spot, which was created for the bliss of souls like mine.\n\nI am so happy, my dear friend, so absorbed in the exquisite sense of mere tranquil existence, that I neglect my talents. I should be incapable of drawing a single stroke at the present moment; and yet I feel that I never was a greater artist than now.\n\nWhen, while the lovely valley teems with vapour around me, and the meridian sun strikes the upper surface of the impenetrable foliage of my trees, and but a few stray gleams steal into the inner sanctuary, I throw myself down among the tall grass by the trickling stream; and, as I lie close to the earth, a thousand unknown plants are noticed by me: when I hear the buzz of the little world among the stalks, and grow familiar with the countless indescribable forms of the insects and flies, then I feel the presence of the Almighty, who formed us in his own image, and the breath\n\nI am so happy, my dear friend, so absorbed in the exquisite sense of mere tranquil existence, that I neglect my talents. I should be incapable of drawing a single stroke at the present moment; and yet I feel that I never was a greater artist than now.\n\nWhen, while the lovely valley teems with vapour around me, and the meridian sun strikes the upper surface of the impenetrable foliage of my trees, and but a few stray gleams steal into the inner sanctuary, I throw myself down among the tall grass by the trickling stream; and, as I lie close to the earth, a thousand unknown plants are noticed by me: when I hear the buzz of the little world among the stalks, and grow familiar with the countless indescribable forms of the insects and flies, then I feel the presence of the Almighty, who formed us in his own image, and the breath\n\nI am so happy, my dear friend, so absorbed in the exquisite sense of mere tranquil existence, that I neglect my talents. I should be incapable of drawing a single stroke at the present moment; and yet I feel that I never was a greater artist than now.\n\nWhen, while the lovely valley teems with vapour around me, and the meridian sun strikes the upper surface of the impenetrable foliage of my trees, and but a few stray gleams steal into the inner sanctuary, I throw myself down among the tall grass by the trickling stream; and, as I lie close to the earth, a thousand unknown plants are noticed by me: when I hear the buzz of the little world among the stalks, and grow familiar with the countless indescribable forms of the insects and flies, then I feel the presence of the Almighty, who formed us in his own image, and the breath", - "created_at": 1608651333 - }, - { - "title": "몽키키키강달찡찡신유술해", - "body": "A wonderful serenity has taken possession of my entire soul, like these sweet mornings of spring which I enjoy with my whole heart. I am alone, and feel the charm of existence in this spot, which was created for the bliss of souls like mine.\n\nI am so happy, my dear friend, so absorbed in the exquisite sense of mere tranquil existence, that I neglect my talents. I should be incapable of drawing a single stroke at the present moment; and yet I feel that I never was a greater artist than now.\n\nWhen, while the lovely valley teems with vapour around me, and the meridian sun strikes the upper surface of the impenetrable foliage of my trees, and but a few stray gleams steal into the inner sanctuary, I throw myself down among the tall grass by the trickling stream; and, as I lie close to the earth, a thousand unknown plants are noticed by me: when I hear the buzz of the little world among the stalks, and grow familiar with the countless indescribable forms of the insects and flies, then I feel the presence of the Almighty, who formed us in his own image, and the breath", - "created_at": 1608651333 - }, - { - "title": "네번째", - "body": "A wonderful serenity has taken possession of my entire soul, like these sweet mornings of spring which I enjoy with my whole heart. I am alone, and feel the charm of existence in this spot, which was created for the bliss of souls like mine.\n\nI am so happy, my dear friend, so absorbed in the exquisite sense of mere tranquil existence, that I neglect my talents. I should be incapable of drawing a single stroke at the present moment; and yet I feel that I never was a greater artist than now.\n\nWhen, while the lovely valley teems with vapour around me, and the meridian sun strikes the upper surface of the impenetrable foliage of my trees, and but a few stray gleams steal into the inner sanctuary, I throw myself down among the tall grass by the trickling stream; and, as I lie close to the earth, a thousand unknown plants are noticed by me: when I hear the buzz of the little world among the stalks, and grow familiar with the countless indescribable forms of the insects and flies, then I feel the presence of the Almighty, who formed us in his own image, and the breath\n\nI am so happy, my dear friend, so absorbed in the exquisite sense of mere tranquil existence, that I neglect my talents. I should be incapable of drawing a single stroke at the present moment; and yet I feel that I never was a greater artist than now.\n\nWhen, while the lovely valley teems with vapour around me, and the meridian sun strikes the upper surface of the impenetrable foliage of my trees, and but a few stray gleams steal into the inner sanctuary, I throw myself down among the tall grass by the trickling stream; and, as I lie close to the earth, a thousand unknown plants are noticed by me: when I hear the buzz of the little world among the stalks, and grow familiar with the countless indescribable forms of the insects and flies, then I feel the presence of the Almighty, who formed us in his own image, and the breath\n\nI am so happy, my dear friend, so absorbed in the exquisite sense of mere tranquil existence, that I neglect my talents. I should be incapable of drawing a single stroke at the present moment; and yet I feel that I never was a greater artist than now.\n\nWhen, while the lovely valley teems with vapour around me, and the meridian sun strikes the upper surface of the impenetrable foliage of my trees, and but a few stray gleams steal into the inner sanctuary, I throw myself down among the tall grass by the trickling stream; and, as I lie close to the earth, a thousand unknown plants are noticed by me: when I hear the buzz of the little world among the stalks, and grow familiar with the countless indescribable forms of the insects and flies, then I feel the presence of the Almighty, who formed us in his own image, and the breath\n\nI am so happy, my dear friend, so absorbed in the exquisite sense of mere tranquil existence, that I neglect my talents. I should be incapable of drawing a single stroke at the present moment; and yet I feel that I never was a greater artist than now.\n\nWhen, while the lovely valley teems with vapour around me, and the meridian sun strikes the upper surface of the impenetrable foliage of my trees, and but a few stray gleams steal into the inner sanctuary, I throw myself down among the tall grass by the trickling stream; and, as I lie close to the earth, a thousand unknown plants are noticed by me: when I hear the buzz of the little world among the stalks, and grow familiar with the countless indescribable forms of the insects and flies, then I feel the presence of the Almighty, who formed us in his own image, and the breath\n\nI am so happy, my dear friend, so absorbed in the exquisite sense of mere tranquil existence, that I neglect my talents. I should be incapable of drawing a single stroke at the present moment; and yet I feel that I never was a greater artist than now.\n\nWhen, while the lovely valley teems with vapour around me, and the meridian sun strikes the upper surface of the impenetrable foliage of my trees, and but a few stray gleams steal into the inner sanctuary, I throw myself down among the tall grass by the trickling stream; and, as I lie close to the earth, a thousand unknown plants are noticed by me: when I hear the buzz of the little world among the stalks, and grow familiar with the countless indescribable forms of the insects and flies, then I feel the presence of the Almighty, who formed us in his own image, and the breath\n\nI am so happy, my dear friend, so absorbed in the exquisite sense of mere tranquil existence, that I neglect my talents. I should be incapable of drawing a single stroke at the present moment; and yet I feel that I never was a greater artist than now.\n\nWhen, while the lovely valley teems with vapour around me, and the meridian sun strikes the upper surface of the impenetrable foliage of my trees, and but a few stray gleams steal into the inner sanctuary, I throw myself down among the tall grass by the trickling stream; and, as I lie close to the earth, a thousand unknown plants are noticed by me: when I hear the buzz of the little world among the stalks, and grow familiar with the countless indescribable forms of the insects and flies, then I feel the presence of the Almighty, who formed us in his own image, and the breath", - "created_at": 1608651333 - }, - { - "title": "승리는 우리의 것", - "body": "A wonderful serenity has taken possession of my entire soul, like these sweet mornings of spring which I enjoy with my whole heart. I am alone, and feel the charm of existence in this spot, which was created for the bliss of souls like mine.\n\nI am so happy, my dear friend, so absorbed in the exquisite sense of mere tranquil existence, that I neglect my talents. I should be incapable of drawing a single stroke at the present moment; and yet I feel that I never was a greater artist than now.\n\nWhen, while the lovely valley teems with vapour around me, and the meridian sun strikes the upper surface of the impenetrable foliage of my trees, and but a few stray gleams steal into the inner sanctuary, I throw myself down among the tall grass by the trickling stream; and, as I lie close to the earth, a thousand unknown plants are noticed by me: when I hear the buzz of the little world among the stalks, and grow familiar with the countless indescribable forms of the insects and flies, then I feel the presence of the Almighty, who formed us in his own image, and the breath", - "created_at": 1608651333 - }, - { - "title": "호롤ㄹ로롤롤로로로롤롤로롤롤ㄹ롤롤 나방이 홓ㄹ로롤롤ㄹ로로로ㅗ롤로롤", - "body": "A wonderful serenity has taken possession of my entire soul, like these sweet mornings of spring which I enjoy with my whole heart. I am alone, and feel the charm of existence in this spot, which was created for the bliss of souls like mine.\n\nI am so happy, my dear friend, so absorbed in the exquisite sense of mere tranquil existence, that I neglect my talents. I should be incapable of drawing a single stroke at the present moment; and yet I feel that I never was a greater artist than now.\n\nWhen, while the lovely valley teems with vapour around me, and the meridian sun strikes the upper surface of the impenetrable foliage of my trees, and but a few stray gleams steal into the inner sanctuary, I throw myself down among the tall grass by the trickling stream; and, as I lie close to the earth, a thousand unknown plants are noticed by me: when I hear the buzz of the little world among the stalks, and grow familiar with the countless indescribable forms of the insects and flies, then I feel the presence of the Almighty, who formed us in his own image, and the breath\n\nI am so happy, my dear friend, so absorbed in the exquisite sense of mere tranquil existence, that I neglect my talents. I should be incapable of drawing a single stroke at the present moment; and yet I feel that I never was a greater artist than now.\n\nWhen, while the lovely valley teems with vapour around me, and the meridian sun strikes the upper surface of the impenetrable foliage of my trees, and but a few stray gleams steal into the inner sanctuary, I throw myself down among the tall grass by the trickling stream; and, as I lie close to the earth, a thousand unknown plants are noticed by me: when I hear the buzz of the little world among the stalks, and grow familiar with the countless indescribable forms of the insects and flies, then I feel the presence of the Almighty, who formed us in his own image, and the breath\n\nI am so happy, my dear friend, so absorbed in the exquisite sense of mere tranquil existence, that I neglect my talents. I should be incapable of drawing a single stroke at the present moment; and yet I feel that I never was a greater artist than now.\n\nWhen, while the lovely valley teems with vapour around me, and the meridian sun strikes the upper surface of the impenetrable foliage of my trees, and but a few stray gleams steal into the inner sanctuary, I throw myself down among the tall grass by the trickling stream; and, as I lie close to the earth, a thousand unknown plants are noticed by me: when I hear the buzz of the little world among the stalks, and grow familiar with the countless indescribable forms of the insects and flies, then I feel the presence of the Almighty, who formed us in his own image, and the breath", - "created_at": 1608651333 - }, - { - "title": "이것은 리스트의 아이템입니다. 쪼매 제목이 길쥬? 그렇습니다. 그래도 뭐... 해야죠 뭐", - "body": "A wonderful serenity has taken possession of my entire soul, like these sweet mornings of spring which I enjoy with my whole heart. I am alone, and feel the charm of existence in this spot, which was created for the bliss of souls like mine.\n\nI am so happy, my dear friend, so absorbed in the exquisite sense of mere tranquil existence, that I neglect my talents. I should be incapable of drawing a single stroke at the present moment; and yet I feel that I never was a greater artist than now.\n\nWhen, while the lovely valley teems with vapour around me, and the meridian sun strikes the upper surface of the impenetrable foliage of my trees, and but a few stray gleams steal into the inner sanctuary, I throw myself down among the tall grass by the trickling stream; and, as I lie close to the earth, a thousand unknown plants are noticed by me: when I hear the buzz of the little world among the stalks, and grow familiar with the countless indescribable forms of the insects and flies, then I feel the presence of the Almighty, who formed us in his own image, and the breath", - "created_at": 1608651333 - }, - { - "title": "우주 외계인 그는 무서운 암흑대왕", - "body": "A wonderful serenity has taken possession of my entire soul, like these sweet mornings of spring which I enjoy with my whole heart. I am alone, and feel the charm of existence in this spot, which was created for the bliss of souls like mine.\n\nI am so happy, my dear friend, so absorbed in the exquisite sense of mere tranquil existence, that I neglect my talents. I should be incapable of drawing a single stroke at the present moment; and yet I feel that I never was a greater artist than now.\n\nWhen, while the lovely valley teems with vapour around me, and the meridian sun strikes the upper surface of the impenetrable foliage of my trees, and but a few stray gleams steal into the inner sanctuary, I throw myself down among the tall grass by the trickling stream; and, as I lie close to the earth, a thousand unknown plants are noticed by me: when I hear the buzz of the little world among the stalks, and grow familiar with the countless indescribable forms of the insects and flies, then I feel the presence of the Almighty, who formed us in his own image, and the breath\n\nI am so happy, my dear friend, so absorbed in the exquisite sense of mere tranquil existence, that I neglect my talents. I should be incapable of drawing a single stroke at the present moment; and yet I feel that I never was a greater artist than now.\n\nWhen, while the lovely valley teems with vapour around me, and the meridian sun strikes the upper surface of the impenetrable foliage of my trees, and but a few stray gleams steal into the inner sanctuary, I throw myself down among the tall grass by the trickling stream; and, as I lie close to the earth, a thousand unknown plants are noticed by me: when I hear the buzz of the little world among the stalks, and grow familiar with the countless indescribable forms of the insects and flies, then I feel the presence of the Almighty, who formed us in his own image, and the breath\n\nI am so happy, my dear friend, so absorbed in the exquisite sense of mere tranquil existence, that I neglect my talents. I should be incapable of drawing a single stroke at the present moment; and yet I feel that I never was a greater artist than now.\n\nWhen, while the lovely valley teems with vapour around me, and the meridian sun strikes the upper surface of the impenetrable foliage of my trees, and but a few stray gleams steal into the inner sanctuary, I throw myself down among the tall grass by the trickling stream; and, as I lie close to the earth, a thousand unknown plants are noticed by me: when I hear the buzz of the little world among the stalks, and grow familiar with the countless indescribable forms of the insects and flies, then I feel the presence of the Almighty, who formed us in his own image, and the breath", - "created_at": 1608651333 - }, - { - "title": "이것은 리스트의 아이템입니다. 쪼매 제목이 길쥬? 그렇습니다. 그래도 뭐... 해야죠 뭐", - "body": "A wonderful serenity has taken possession of my entire soul, like these sweet mornings of spring which I enjoy with my whole heart. I am alone, and feel the charm of existence in this spot, which was created for the bliss of souls like mine.\n\nI am so happy, my dear friend, so absorbed in the exquisite sense of mere tranquil existence, that I neglect my talents. I should be incapable of drawing a single stroke at the present moment; and yet I feel that I never was a greater artist than now.\n\nWhen, while the lovely valley teems with vapour around me, and the meridian sun strikes the upper surface of the impenetrable foliage of my trees, and but a few stray gleams steal into the inner sanctuary, I throw myself down among the tall grass by the trickling stream; and, as I lie close to the earth, a thousand unknown plants are noticed by me: when I hear the buzz of the little world among the stalks, and grow familiar with the countless indescribable forms of the insects and flies, then I feel the presence of the Almighty, who formed us in his own image, and the breath", - "created_at": 1608651333 - }, - { - "title": "하나면 하나지 둘이겠느냐~", - "body": "A wonderful serenity has taken possession of my entire soul, like these sweet mornings of spring which I enjoy with my whole heart. I am alone, and feel the charm of existence in this spot, which was created for the bliss of souls like mine.\n\nI am so happy, my dear friend, so absorbed in the exquisite sense of mere tranquil existence, that I neglect my talents. I should be incapable of drawing a single stroke at the present moment; and yet I feel that I never was a greater artist than now.\n\nWhen, while the lovely valley teems with vapour around me, and the meridian sun strikes the upper surface of the impenetrable foliage of my trees, and but a few stray gleams steal into the inner sanctuary, I throw myself down among the tall grass by the trickling stream; and, as I lie close to the earth, a thousand unknown plants are noticed by me: when I hear the buzz of the little world among the stalks, and grow familiar with the countless indescribable forms of the insects and flies, then I feel the presence of the Almighty, who formed us in his own image, and the breath", - "created_at": 1608651333 - }, - { - "title": "더 내려가봐유?", - "body": "A wonderful serenity has taken possession of my entire soul, like these sweet mornings of spring which I enjoy with my whole heart. I am alone, and feel the charm of existence in this spot, which was created for the bliss of souls like mine.\n\nI am so happy, my dear friend, so absorbed in the exquisite sense of mere tranquil existence, that I neglect my talents. I should be incapable of drawing a single stroke at the present moment; and yet I feel that I never was a greater artist than now.\n\nWhen, while the lovely valley teems with vapour around me, and the meridian sun strikes the upper surface of the impenetrable foliage of my trees, and but a few stray gleams steal into the inner sanctuary, I throw myself down among the tall grass by the trickling stream; and, as I lie close to the earth, a thousand unknown plants are noticed by me: when I hear the buzz of the little world among the stalks, and grow familiar with the countless indescribable forms of the insects and flies, then I feel the presence of the Almighty, who formed us in his own image, and the breath", - "created_at": 1608651333 - }, - { - "title": "이것은 리스트의 아이템입니다. 쪼매 제목이 길쥬? 그렇습니다. 그래도 뭐... 해야죠 뭐", - "body": "A wonderful serenity has taken possession of my entire soul, like these sweet mornings of spring which I enjoy with my whole heart. I am alone, and feel the charm of existence in this spot, which was created for the bliss of souls like mine.\n\nI am so happy, my dear friend, so absorbed in the exquisite sense of mere tranquil existence, that I neglect my talents. I should be incapable of drawing a single stroke at the present moment; and yet I feel that I never was a greater artist than now.\n\nWhen, while the lovely valley teems with vapour around me, and the meridian sun strikes the upper surface of the impenetrable foliage of my trees, and but a few stray gleams steal into the inner sanctuary, I throw myself down among the tall grass by the trickling stream; and, as I lie close to the earth, a thousand unknown plants are noticed by me: when I hear the buzz of the little world among the stalks, and grow familiar with the countless indescribable forms of the insects and flies, then I feel the presence of the Almighty, who formed us in his own image, and the breath\n\nI am so happy, my dear friend, so absorbed in the exquisite sense of mere tranquil existence, that I neglect my talents. I should be incapable of drawing a single stroke at the present moment; and yet I feel that I never was a greater artist than now.\n\nWhen, while the lovely valley teems with vapour around me, and the meridian sun strikes the upper surface of the impenetrable foliage of my trees, and but a few stray gleams steal into the inner sanctuary, I throw myself down among the tall grass by the trickling stream; and, as I lie close to the earth, a thousand unknown plants are noticed by me: when I hear the buzz of the little world among the stalks, and grow familiar with the countless indescribable forms of the insects and flies, then I feel the presence of the Almighty, who formed us in his own image, and the breath\n\nI am so happy, my dear friend, so absorbed in the exquisite sense of mere tranquil existence, that I neglect my talents. I should be incapable of drawing a single stroke at the present moment; and yet I feel that I never was a greater artist than now.\n\nWhen, while the lovely valley teems with vapour around me, and the meridian sun strikes the upper surface of the impenetrable foliage of my trees, and but a few stray gleams steal into the inner sanctuary, I throw myself down among the tall grass by the trickling stream; and, as I lie close to the earth, a thousand unknown plants are noticed by me: when I hear the buzz of the little world among the stalks, and grow familiar with the countless indescribable forms of the insects and flies, then I feel the presence of the Almighty, who formed us in his own image, and the breath", - "created_at": 1608651333 - }, - { - "title": "이것은 리스트의 아이템입니다. 쪼매 제목이 길쥬? 그렇습니다. 그래도 뭐... 해야죠 뭐", - "body": "A wonderful serenity has taken possession of my entire soul, like these sweet mornings of spring which I enjoy with my whole heart. I am alone, and feel the charm of existence in this spot, which was created for the bliss of souls like mine.\n\nI am so happy, my dear friend, so absorbed in the exquisite sense of mere tranquil existence, that I neglect my talents. I should be incapable of drawing a single stroke at the present moment; and yet I feel that I never was a greater artist than now.\n\nWhen, while the lovely valley teems with vapour around me, and the meridian sun strikes the upper surface of the impenetrable foliage of my trees, and but a few stray gleams steal into the inner sanctuary, I throw myself down among the tall grass by the trickling stream; and, as I lie close to the earth, a thousand unknown plants are noticed by me: when I hear the buzz of the little world among the stalks, and grow familiar with the countless indescribable forms of the insects and flies, then I feel the presence of the Almighty, who formed us in his own image, and the breath", - "created_at": 1608651333 - }, - { - "title": "이것은 리스트의 아이템입니다. 쪼매 제목이 길쥬? 그렇습니다. 그래도 뭐... 해야죠 뭐", - "body": "A wonderful serenity has taken possession of my entire soul, like these sweet mornings of spring which I enjoy with my whole heart. I am alone, and feel the charm of existence in this spot, which was created for the bliss of souls like mine.\n\nI am so happy, my dear friend, so absorbed in the exquisite sense of mere tranquil existence, that I neglect my talents. I should be incapable of drawing a single stroke at the present moment; and yet I feel that I never was a greater artist than now.\n\nWhen, while the lovely valley teems with vapour around me, and the meridian sun strikes the upper surface of the impenetrable foliage of my trees, and but a few stray gleams steal into the inner sanctuary, I throw myself down among the tall grass by the trickling stream; and, as I lie close to the earth, a thousand unknown plants are noticed by me: when I hear the buzz of the little world among the stalks, and grow familiar with the countless indescribable forms of the insects and flies, then I feel the presence of the Almighty, who formed us in his own image, and the breath", - "created_at": 1608651333 - }, - { - "title": "이것은 리스트의 아이템입니다. 쪼매 제목이 길쥬? 그렇습니다. 그래도 뭐... 해야죠 뭐", - "body": "A wonderful serenity has taken possession of my entire soul, like these sweet mornings of spring which I enjoy with my whole heart. I am alone, and feel the charm of existence in this spot, which was created for the bliss of souls like mine.\n\nI am so happy, my dear friend, so absorbed in the exquisite sense of mere tranquil existence, that I neglect my talents. I should be incapable of drawing a single stroke at the present moment; and yet I feel that I never was a greater artist than now.\n\nWhen, while the lovely valley teems with vapour around me, and the meridian sun strikes the upper surface of the impenetrable foliage of my trees, and but a few stray gleams steal into the inner sanctuary, I throw myself down among the tall grass by the trickling stream; and, as I lie close to the earth, a thousand unknown plants are noticed by me: when I hear the buzz of the little world among the stalks, and grow familiar with the countless indescribable forms of the insects and flies, then I feel the presence of the Almighty, who formed us in his own image, and the breath\n\nI am so happy, my dear friend, so absorbed in the exquisite sense of mere tranquil existence, that I neglect my talents. I should be incapable of drawing a single stroke at the present moment; and yet I feel that I never was a greater artist than now.\n\nWhen, while the lovely valley teems with vapour around me, and the meridian sun strikes the upper surface of the impenetrable foliage of my trees, and but a few stray gleams steal into the inner sanctuary, I throw myself down among the tall grass by the trickling stream; and, as I lie close to the earth, a thousand unknown plants are noticed by me: when I hear the buzz of the little world among the stalks, and grow familiar with the countless indescribable forms of the insects and flies, then I feel the presence of the Almighty, who formed us in his own image, and the breath\n\nI am so happy, my dear friend, so absorbed in the exquisite sense of mere tranquil existence, that I neglect my talents. I should be incapable of drawing a single stroke at the present moment; and yet I feel that I never was a greater artist than now.\n\nWhen, while the lovely valley teems with vapour around me, and the meridian sun strikes the upper surface of the impenetrable foliage of my trees, and but a few stray gleams steal into the inner sanctuary, I throw myself down among the tall grass by the trickling stream; and, as I lie close to the earth, a thousand unknown plants are noticed by me: when I hear the buzz of the little world among the stalks, and grow familiar with the countless indescribable forms of the insects and flies, then I feel the presence of the Almighty, who formed us in his own image, and the breath", - "created_at": 1608651333 - } -] \ No newline at end of file diff --git a/Diary/Resource/Diary.xcdatamodeld/Diary.xcdatamodel/contents b/Diary/Resource/Diary.xcdatamodeld/Diary.xcdatamodel/contents index 50d2514e8..082120e3d 100644 --- a/Diary/Resource/Diary.xcdatamodeld/Diary.xcdatamodel/contents +++ b/Diary/Resource/Diary.xcdatamodeld/Diary.xcdatamodel/contents @@ -1,4 +1,9 @@ - - + + + + + + + \ No newline at end of file diff --git a/Diary/Utility/ActivityViewPresentable.swift b/Diary/Utility/ActivityViewPresentable.swift new file mode 100644 index 000000000..d70d47c99 --- /dev/null +++ b/Diary/Utility/ActivityViewPresentable.swift @@ -0,0 +1,22 @@ +// +// ActivityViewPresentable.swift +// Diary +// +// Created by Mary & Whales on 2023/09/08. +// + +import UIKit + +protocol ActivityViewPresentable where Self: UIViewController { + func presentActivityView(shareItem: String) +} + +extension ActivityViewPresentable { + func presentActivityView(shareItem: String) { + let activityViewController = UIActivityViewController( + activityItems: [shareItem], + applicationActivities: []) + + present(activityViewController, animated: true) + } +} diff --git a/Diary/Utility/DiaryEditable.swift b/Diary/Utility/DiaryEditable.swift new file mode 100644 index 000000000..2a190f692 --- /dev/null +++ b/Diary/Utility/DiaryEditable.swift @@ -0,0 +1,16 @@ +// +// DiaryEditable.swift +// Diary +// +// Created by Mary & Whales on 2023/09/17. +// + +import Foundation + +protocol DiaryEditable { + var diaryContents: [DiaryContent] { get } + func refresh() throws + func insert(diaryContent: DiaryContent) throws + func update(diaryContent: DiaryContent) throws + func delete(id: UUID) throws +} diff --git a/Diary/Utility/Logger.swift b/Diary/Utility/Logger.swift new file mode 100644 index 000000000..fa33ea64c --- /dev/null +++ b/Diary/Utility/Logger.swift @@ -0,0 +1,14 @@ +// +// Logger.swift +// Diary +// +// Created by Mary & Whales on 9/15/23. +// + +import OSLog + +struct Logger { + func osLog(_ message: String) { + os_log("%@", message) + } +} diff --git a/Diary/View/DiaryTableViewCell.swift b/Diary/View/DiaryTableViewCell.swift index ab377f636..d3f241cc6 100644 --- a/Diary/View/DiaryTableViewCell.swift +++ b/Diary/View/DiaryTableViewCell.swift @@ -21,6 +21,7 @@ final class DiaryTableViewCell: UITableViewCell { label.font = .preferredFont(forTextStyle: .body) label.adjustsFontForContentSizeCategory = true label.setContentCompressionResistancePriority(.required, for: .horizontal) + label.setContentHuggingPriority(.defaultHigh, for: .horizontal) return label }() @@ -45,6 +46,7 @@ final class DiaryTableViewCell: UITableViewCell { private let descriptionStackView: UIStackView = { let stackView = UIStackView() stackView.axis = .horizontal + stackView.alignment = .center stackView.spacing = 5 return stackView @@ -61,9 +63,9 @@ final class DiaryTableViewCell: UITableViewCell { } func configureCell(data: DiaryContent) { - titleLabel.text = data.title + titleLabel.text = data.title.isEmpty ? "noTitle".localized : data.title dateLabel.text = data.date - previewLabel.text = data.body + previewLabel.text = data.body.trimmingCharacters(in: .whitespacesAndNewlines) } private func configureUI() { @@ -75,10 +77,10 @@ final class DiaryTableViewCell: UITableViewCell { contentView.addSubview(titleStackView) accessoryType = .disclosureIndicator - setUpConstraints() + setupConstraints() } - private func setUpConstraints() { + private func setupConstraints() { NSLayoutConstraint.activate([ titleStackView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 5), titleStackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -5), diff --git a/README.md b/README.md index fb38a0861..e5ba3608a 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,8 @@ ## 👀 Diagram ### 📐 UML - + +
@@ -100,7 +101,7 @@
-### 2️⃣ DiaryManager +### 2️⃣ DiaryManager (STEP2 진행 후 수정 예정) `Asset`에 넣은 `"sample"` 데이터를 받아오는 메서드를 구현하는데, 이 작업은 `View`나 `Controller`의 역할이 아니라고 생각해서 `DiaryManager`라는 객체를 `Model`로 만들었습니다. `DiaryViewController`가 `DiaryManager`를 가지고 있고, `DiaryManager`가 데이터 `fetch` 작업을 할 수 있도록 메서드를 구현하였습니다.
수정 전 @@ -178,6 +179,7 @@ extension DiaryViewController: UITableViewDataSource {
+ ### 3️⃣ SwiftLint 활용 더 깔끔하고 통일성있는 컨벤션을 위해 SwiftLint를 적용했습니다. @@ -193,19 +195,23 @@ extension DiaryViewController: UITableViewDataSource { - trailing_whitespace - trailing_comma ``` -
+ ## 📚 참고 링크 - [🍎Apple Docs: os_log](https://developer.apple.com/documentation/os/os_log) -- [🍎Apple Docs: UIScrollView.KeyboardDismissMode](https://developer.apple.com/documentation/uikit/uiscrollview/keyboarddismissmodee) +- [🍎Apple Docs: UIScrollView.KeyboardDismissMode](https://developer.apple.com/documentation/uikit/uiscrollview/1619437-keyboarddismissmode) - [🍎Apple Docs: Adaptivity and Layout](https://developer.apple.com/design/human-interface-guidelines/layout) - [🍎Apple Docs: DateFormatter](https://developer.apple.com/documentation/foundation/dateformatter) - [🍎Apple Docs: UITextView](https://developer.apple.com/documentation/uikit/uitextview) +- [🍎Apple Docs: Swipe-trailing](https://developer.apple.com/documentation/uikit/uitableviewdelegate/2902367-tableview) +- [🍎Apple Docs: UIActivityViewController](https://developer.apple.com/documentation/uikit/uiactivityviewcontroller) - [🍏WWDC: Making Apps Adaptive, Part 1](https://asciiwwdc.com/2016/sessions/222) - [🍏WWDC: Making Apps Adaptive, Part 2](https://www.youtube.com/watch?v=s3utpBiRbB0) - [🍏WWDC note: UIKit: Apps for Every Size and Shape](https://www.wwdcnotes.com/notes/wwdc18/235/) - [SwiftLint Rule Directory](https://realm.github.io/SwiftLint/rule-directory.html) +- [야곰닷넷: Test Double](https://yagom.net/courses/unit-test-작성하기/lessons/테스트를-위한-객체-만들기/topic/테스트를-위한-객체를-이용해서-테스트-작성하기/) +- [blog: URL 처리 방법](https://ios-development.tistory.com/1014)
@@ -217,5 +223,4 @@ extension DiaryViewController: UITableViewDataSource { | :--------: | :--------: | :--------: | | | **🐿️Mary🐿️** | **https://github.com/MaryJo-github** | - - [타임라인 링크](https://github.com/WhalesJin/ios-diary/wiki/타임라인) diff --git a/en.lproj/Localizable.strings b/en.lproj/Localizable.strings index 66ad0685b..acc239125 100644 --- a/en.lproj/Localizable.strings +++ b/en.lproj/Localizable.strings @@ -7,3 +7,25 @@ */ "title" = "Diary"; +"NoTitle" = "No Title"; + +"failedFetchDataAlertTitle" = "Failed Fetch Data"; +"failedFetchDataAlertMessage" = "Try again"; +"failedFetchDataAlertAction" = "Yes"; + +"failedDeleteDataAlertTitle" = "Failed Delete Data"; +"failedDeleteDataAlertMessage" = "Try again"; +"failedDeleteDataAlertAction" = "Yes"; + +"failedSaveDataAlertTitle" = "Failed Save Data"; +"failedSaveDataAlertMessage" = "Try again"; +"failedSaveDataAlertAction" = "Yes"; + +"checkDeleteAlertTitle" = "Really?"; +"checkDeleteAlertMessage" = "Are you sure?"; +"checkDeleteAlertCancelAction" = "No"; +"checkDeleteAlertAction" = "Yes"; + +"share" = "Share..."; +"delete" = "Delete"; +"cancel" = "Cancel"; diff --git a/ko.lproj/Localizable.strings b/ko.lproj/Localizable.strings index 6361b9c70..fd27edd64 100644 --- a/ko.lproj/Localizable.strings +++ b/ko.lproj/Localizable.strings @@ -7,3 +7,25 @@ */ "title" = "일기장"; +"noTitle" = "제목없음"; + +"failedFetchDataAlertTitle" = "데이터 불러오기 실패"; +"failedFetchDataAlertMessage" = "앱을 다시 실행해주십시오."; +"failedFetchDataAlertAction" = "확인"; + +"failedDeleteDataAlertTitle" = "데이터 삭제 실패"; +"failedDeleteDataAlertMessage" = "다시 시도해주십시오."; +"failedDeleteDataAlertAction" = "확인"; + +"failedSaveDataAlertTitle" = "데이터 저장 실패"; +"failedSaveDataAlertMessage" = "다시 시도해주십시오."; +"failedSaveDataAlertAction" = "확인"; + +"checkDeleteAlertTitle" = "진짜요?"; +"checkDeleteAlertMessage" = "정말로 삭제하시겠어요?"; +"checkDeleteAlertCancelAction" = "취소"; +"checkDeleteAlertAction" = "삭제"; + +"share" = "공유..."; +"delete" = "삭제"; +"cancel" = "취소";