From 04eb67bcb4b68fb8574abf4e182789edd1a07baf Mon Sep 17 00:00:00 2001 From: Moon <1tjen@naver.com> Date: Sun, 24 Sep 2023 21:16:29 +0900 Subject: [PATCH 01/48] =?UTF-8?q?chore:=20MVVM=20=ED=8F=B4=EB=8D=94=20?= =?UTF-8?q?=EA=B5=AC=EC=A1=B0=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ProjectManager.xcodeproj/project.pbxproj | 50 +++++++++++++++++-- .../{ => Application}/AppDelegate.swift | 0 .../{ => Application}/SceneDelegate.swift | 0 .../AccentColor.colorset/Contents.json | 0 .../AppIcon.appiconset/Contents.json | 0 .../Assets.xcassets/Contents.json | 0 .../Base.lproj/LaunchScreen.storyboard | 0 .../{ => View}/ViewController.swift | 0 8 files changed, 45 insertions(+), 5 deletions(-) rename ProjectManager/ProjectManager/{ => Application}/AppDelegate.swift (100%) rename ProjectManager/ProjectManager/{ => Application}/SceneDelegate.swift (100%) rename ProjectManager/ProjectManager/{ => Resource}/Assets.xcassets/AccentColor.colorset/Contents.json (100%) rename ProjectManager/ProjectManager/{ => Resource}/Assets.xcassets/AppIcon.appiconset/Contents.json (100%) rename ProjectManager/ProjectManager/{ => Resource}/Assets.xcassets/Contents.json (100%) rename ProjectManager/ProjectManager/{ => View}/Base.lproj/LaunchScreen.storyboard (100%) rename ProjectManager/ProjectManager/{ => View}/ViewController.swift (100%) diff --git a/ProjectManager/ProjectManager.xcodeproj/project.pbxproj b/ProjectManager/ProjectManager.xcodeproj/project.pbxproj index fa92d9520..077a270ac 100644 --- a/ProjectManager/ProjectManager.xcodeproj/project.pbxproj +++ b/ProjectManager/ProjectManager.xcodeproj/project.pbxproj @@ -35,6 +35,46 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 2897D3752AC05F8400662BAA /* ViewModel */ = { + isa = PBXGroup; + children = ( + ); + path = ViewModel; + sourceTree = ""; + }; + 2897D3762AC05F8D00662BAA /* View */ = { + isa = PBXGroup; + children = ( + C7431F1025F51E1E0094C4CF /* LaunchScreen.storyboard */, + C7431F0925F51E1D0094C4CF /* ViewController.swift */, + ); + path = View; + sourceTree = ""; + }; + 2897D3772AC05F9500662BAA /* Model */ = { + isa = PBXGroup; + children = ( + ); + path = Model; + sourceTree = ""; + }; + 2897D3782AC05F9D00662BAA /* Application */ = { + isa = PBXGroup; + children = ( + C7431F0525F51E1D0094C4CF /* AppDelegate.swift */, + C7431F0725F51E1D0094C4CF /* SceneDelegate.swift */, + ); + path = Application; + sourceTree = ""; + }; + 2897D3792AC05FBE00662BAA /* Resource */ = { + isa = PBXGroup; + children = ( + C7431F0E25F51E1E0094C4CF /* Assets.xcassets */, + ); + path = Resource; + sourceTree = ""; + }; C7431EF925F51E1D0094C4CF = { isa = PBXGroup; children = ( @@ -54,11 +94,11 @@ C7431F0425F51E1D0094C4CF /* ProjectManager */ = { isa = PBXGroup; children = ( - C7431F0525F51E1D0094C4CF /* AppDelegate.swift */, - C7431F0725F51E1D0094C4CF /* SceneDelegate.swift */, - C7431F0925F51E1D0094C4CF /* ViewController.swift */, - C7431F0E25F51E1E0094C4CF /* Assets.xcassets */, - C7431F1025F51E1E0094C4CF /* LaunchScreen.storyboard */, + 2897D3782AC05F9D00662BAA /* Application */, + 2897D3772AC05F9500662BAA /* Model */, + 2897D3762AC05F8D00662BAA /* View */, + 2897D3752AC05F8400662BAA /* ViewModel */, + 2897D3792AC05FBE00662BAA /* Resource */, C7431F1325F51E1E0094C4CF /* Info.plist */, ); path = ProjectManager; diff --git a/ProjectManager/ProjectManager/AppDelegate.swift b/ProjectManager/ProjectManager/Application/AppDelegate.swift similarity index 100% rename from ProjectManager/ProjectManager/AppDelegate.swift rename to ProjectManager/ProjectManager/Application/AppDelegate.swift diff --git a/ProjectManager/ProjectManager/SceneDelegate.swift b/ProjectManager/ProjectManager/Application/SceneDelegate.swift similarity index 100% rename from ProjectManager/ProjectManager/SceneDelegate.swift rename to ProjectManager/ProjectManager/Application/SceneDelegate.swift diff --git a/ProjectManager/ProjectManager/Assets.xcassets/AccentColor.colorset/Contents.json b/ProjectManager/ProjectManager/Resource/Assets.xcassets/AccentColor.colorset/Contents.json similarity index 100% rename from ProjectManager/ProjectManager/Assets.xcassets/AccentColor.colorset/Contents.json rename to ProjectManager/ProjectManager/Resource/Assets.xcassets/AccentColor.colorset/Contents.json diff --git a/ProjectManager/ProjectManager/Assets.xcassets/AppIcon.appiconset/Contents.json b/ProjectManager/ProjectManager/Resource/Assets.xcassets/AppIcon.appiconset/Contents.json similarity index 100% rename from ProjectManager/ProjectManager/Assets.xcassets/AppIcon.appiconset/Contents.json rename to ProjectManager/ProjectManager/Resource/Assets.xcassets/AppIcon.appiconset/Contents.json diff --git a/ProjectManager/ProjectManager/Assets.xcassets/Contents.json b/ProjectManager/ProjectManager/Resource/Assets.xcassets/Contents.json similarity index 100% rename from ProjectManager/ProjectManager/Assets.xcassets/Contents.json rename to ProjectManager/ProjectManager/Resource/Assets.xcassets/Contents.json diff --git a/ProjectManager/ProjectManager/Base.lproj/LaunchScreen.storyboard b/ProjectManager/ProjectManager/View/Base.lproj/LaunchScreen.storyboard similarity index 100% rename from ProjectManager/ProjectManager/Base.lproj/LaunchScreen.storyboard rename to ProjectManager/ProjectManager/View/Base.lproj/LaunchScreen.storyboard diff --git a/ProjectManager/ProjectManager/ViewController.swift b/ProjectManager/ProjectManager/View/ViewController.swift similarity index 100% rename from ProjectManager/ProjectManager/ViewController.swift rename to ProjectManager/ProjectManager/View/ViewController.swift From d1af0ff6ae30da0bd498583af555b09fbbf1e370 Mon Sep 17 00:00:00 2001 From: Moon <1tjen@naver.com> Date: Mon, 25 Sep 2023 07:58:47 +0900 Subject: [PATCH 02/48] =?UTF-8?q?refactor:=20ViewController=EC=97=90?= =?UTF-8?q?=EC=84=9C=20ListViewController=EB=A1=9C=20=EB=84=A4=EC=9D=B4?= =?UTF-8?q?=EB=B0=8D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ProjectManager/ProjectManager/Application/SceneDelegate.swift | 2 +- ProjectManager/ProjectManager/View/ViewController.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ProjectManager/ProjectManager/Application/SceneDelegate.swift b/ProjectManager/ProjectManager/Application/SceneDelegate.swift index 1e63da054..f19eb9fab 100644 --- a/ProjectManager/ProjectManager/Application/SceneDelegate.swift +++ b/ProjectManager/ProjectManager/Application/SceneDelegate.swift @@ -15,7 +15,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { guard let windowScene = (scene as? UIWindowScene) else { return } window = UIWindow(windowScene: windowScene) - window?.rootViewController = ViewController() + window?.rootViewController = ListViewController() window?.makeKeyAndVisible() } diff --git a/ProjectManager/ProjectManager/View/ViewController.swift b/ProjectManager/ProjectManager/View/ViewController.swift index e3e7d65db..56ff8507a 100644 --- a/ProjectManager/ProjectManager/View/ViewController.swift +++ b/ProjectManager/ProjectManager/View/ViewController.swift @@ -6,7 +6,7 @@ import UIKit -class ViewController: UIViewController { +final class ListViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() From 4749fcca1c63f6abe94c799a0e905d3cb6c73f84 Mon Sep 17 00:00:00 2001 From: Moon <1tjen@naver.com> Date: Mon, 25 Sep 2023 07:59:30 +0900 Subject: [PATCH 03/48] =?UTF-8?q?feat:=20ListViewController=EC=97=90=20tab?= =?UTF-8?q?leView=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ProjectManager/View/ViewController.swift | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/ProjectManager/ProjectManager/View/ViewController.swift b/ProjectManager/ProjectManager/View/ViewController.swift index 56ff8507a..c5d4dd9c2 100644 --- a/ProjectManager/ProjectManager/View/ViewController.swift +++ b/ProjectManager/ProjectManager/View/ViewController.swift @@ -7,9 +7,63 @@ import UIKit final class ListViewController: UIViewController { + private lazy var tableView: UITableView = { + let tableView = UITableView() + tableView.dataSource = self + tableView.delegate = self + tableView.backgroundColor = .orange + + return tableView + }() + override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .yellow + + configureUI() + } +} + +// MARK: - Configure UI +extension ListViewController { + private func configureUI() { + addSubviews() + setUpTableViewConstraints() + } + + private func addSubviews() { + view.addSubview(tableView) + } + + private func setUpTableViewConstraints() { + tableView.translatesAutoresizingMaskIntoConstraints = false + + NSLayoutConstraint.activate([ + tableView.leadingAnchor + .constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor), + tableView.trailingAnchor + .constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor), + tableView.topAnchor + .constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), + tableView.bottomAnchor + .constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor) + ]) + } +} + +// MARK: - Table View Data Source +extension ListViewController: UITableViewDataSource { + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return 1 } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + return UITableViewCell() + } +} + +// MARK: - Table View Delegate +extension ListViewController: UITableViewDelegate { + } From 9ecf43df58943831095347f1c6ee77c042da4e4a Mon Sep 17 00:00:00 2001 From: Moon <1tjen@naver.com> Date: Mon, 25 Sep 2023 08:00:41 +0900 Subject: [PATCH 04/48] =?UTF-8?q?chore:=20ListViewController=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20=EC=9D=B4=EB=A6=84=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ProjectManager/ProjectManager.xcodeproj/project.pbxproj | 8 ++++---- .../{ViewController.swift => ListViewController.swift} | 0 2 files changed, 4 insertions(+), 4 deletions(-) rename ProjectManager/ProjectManager/View/{ViewController.swift => ListViewController.swift} (100%) diff --git a/ProjectManager/ProjectManager.xcodeproj/project.pbxproj b/ProjectManager/ProjectManager.xcodeproj/project.pbxproj index 077a270ac..be4b2f19e 100644 --- a/ProjectManager/ProjectManager.xcodeproj/project.pbxproj +++ b/ProjectManager/ProjectManager.xcodeproj/project.pbxproj @@ -9,7 +9,7 @@ /* Begin PBXBuildFile section */ C7431F0625F51E1D0094C4CF /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7431F0525F51E1D0094C4CF /* AppDelegate.swift */; }; C7431F0825F51E1D0094C4CF /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7431F0725F51E1D0094C4CF /* SceneDelegate.swift */; }; - C7431F0A25F51E1D0094C4CF /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7431F0925F51E1D0094C4CF /* ViewController.swift */; }; + C7431F0A25F51E1D0094C4CF /* ListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7431F0925F51E1D0094C4CF /* ListViewController.swift */; }; C7431F0F25F51E1E0094C4CF /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C7431F0E25F51E1E0094C4CF /* Assets.xcassets */; }; C7431F1225F51E1E0094C4CF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C7431F1025F51E1E0094C4CF /* LaunchScreen.storyboard */; }; /* End PBXBuildFile section */ @@ -18,7 +18,7 @@ C7431F0225F51E1D0094C4CF /* ProjectManager.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ProjectManager.app; sourceTree = BUILT_PRODUCTS_DIR; }; C7431F0525F51E1D0094C4CF /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; C7431F0725F51E1D0094C4CF /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; - C7431F0925F51E1D0094C4CF /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; + C7431F0925F51E1D0094C4CF /* ListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListViewController.swift; sourceTree = ""; }; C7431F0E25F51E1E0094C4CF /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; C7431F1125F51E1E0094C4CF /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; C7431F1325F51E1E0094C4CF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -46,7 +46,7 @@ isa = PBXGroup; children = ( C7431F1025F51E1E0094C4CF /* LaunchScreen.storyboard */, - C7431F0925F51E1D0094C4CF /* ViewController.swift */, + C7431F0925F51E1D0094C4CF /* ListViewController.swift */, ); path = View; sourceTree = ""; @@ -173,7 +173,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - C7431F0A25F51E1D0094C4CF /* ViewController.swift in Sources */, + C7431F0A25F51E1D0094C4CF /* ListViewController.swift in Sources */, C7431F0625F51E1D0094C4CF /* AppDelegate.swift in Sources */, C7431F0825F51E1D0094C4CF /* SceneDelegate.swift in Sources */, ); diff --git a/ProjectManager/ProjectManager/View/ViewController.swift b/ProjectManager/ProjectManager/View/ListViewController.swift similarity index 100% rename from ProjectManager/ProjectManager/View/ViewController.swift rename to ProjectManager/ProjectManager/View/ListViewController.swift From 55d04a8fdbfe00ef264eb1be9f78529e59ca4fd1 Mon Sep 17 00:00:00 2001 From: Moon <1tjen@naver.com> Date: Mon, 25 Sep 2023 08:26:47 +0900 Subject: [PATCH 05/48] =?UTF-8?q?feat:=20ListViewCell=20=EC=83=9D=EC=84=B1?= =?UTF-8?q?=20=EB=B0=8F=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ProjectManager.xcodeproj/project.pbxproj | 4 + .../ProjectManager/View/ListViewCell.swift | 86 +++++++++++++++++++ .../View/ListViewController.swift | 3 +- 3 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 ProjectManager/ProjectManager/View/ListViewCell.swift diff --git a/ProjectManager/ProjectManager.xcodeproj/project.pbxproj b/ProjectManager/ProjectManager.xcodeproj/project.pbxproj index be4b2f19e..2fc007a64 100644 --- a/ProjectManager/ProjectManager.xcodeproj/project.pbxproj +++ b/ProjectManager/ProjectManager.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 28CA66E52AC0F72600FFD7A9 /* ListViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28CA66E42AC0F72600FFD7A9 /* ListViewCell.swift */; }; C7431F0625F51E1D0094C4CF /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7431F0525F51E1D0094C4CF /* AppDelegate.swift */; }; C7431F0825F51E1D0094C4CF /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7431F0725F51E1D0094C4CF /* SceneDelegate.swift */; }; C7431F0A25F51E1D0094C4CF /* ListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7431F0925F51E1D0094C4CF /* ListViewController.swift */; }; @@ -15,6 +16,7 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 28CA66E42AC0F72600FFD7A9 /* ListViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListViewCell.swift; sourceTree = ""; }; C7431F0225F51E1D0094C4CF /* ProjectManager.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ProjectManager.app; sourceTree = BUILT_PRODUCTS_DIR; }; C7431F0525F51E1D0094C4CF /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; C7431F0725F51E1D0094C4CF /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; @@ -47,6 +49,7 @@ children = ( C7431F1025F51E1E0094C4CF /* LaunchScreen.storyboard */, C7431F0925F51E1D0094C4CF /* ListViewController.swift */, + 28CA66E42AC0F72600FFD7A9 /* ListViewCell.swift */, ); path = View; sourceTree = ""; @@ -173,6 +176,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 28CA66E52AC0F72600FFD7A9 /* ListViewCell.swift in Sources */, C7431F0A25F51E1D0094C4CF /* ListViewController.swift in Sources */, C7431F0625F51E1D0094C4CF /* AppDelegate.swift in Sources */, C7431F0825F51E1D0094C4CF /* SceneDelegate.swift in Sources */, diff --git a/ProjectManager/ProjectManager/View/ListViewCell.swift b/ProjectManager/ProjectManager/View/ListViewCell.swift new file mode 100644 index 000000000..109c88cd0 --- /dev/null +++ b/ProjectManager/ProjectManager/View/ListViewCell.swift @@ -0,0 +1,86 @@ +// +// ListViewCell.swift +// ProjectManager +// +// Created by Moon on 2023/09/25. +// + +import UIKit + +final class ListViewCell: UITableViewCell { + private let titleLabel: UILabel = { + let label = UILabel() + label.font = UIFont.preferredFont(forTextStyle: .title3) + label.text = "책상정리" + + return label + }() + + private let descriptionLabel: UILabel = { + let label = UILabel() + label.font = UIFont.preferredFont(forTextStyle: .body) + label.textColor = .systemGray3 + label.text = "책상정리..." + label.numberOfLines = 3 + + return label + }() + + private let dateLabel: UILabel = { + let label = UILabel() + label.font = UIFont.preferredFont(forTextStyle: .callout) + label.text = "2023. 09. 25." + + return label + }() + + private let contentStackView: UIStackView = { + let stackView = UIStackView() + stackView.axis = .vertical + stackView.alignment = .leading + + return stackView + }() + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + + configureUI() + backgroundColor = .brown + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +// MARK: - Configure UI +extension ListViewCell { + private func configureUI() { + addSubviews() + setUpContentStackViewConstraints() + } + + private func addSubviews() { + [titleLabel, descriptionLabel, dateLabel].forEach { + contentStackView.addArrangedSubview($0) + } + + addSubview(contentStackView) + } + + private func setUpContentStackViewConstraints() { + contentStackView.translatesAutoresizingMaskIntoConstraints = false + + NSLayoutConstraint.activate([ + contentStackView.leadingAnchor + .constraint(equalTo: leadingAnchor, constant: 8), + contentStackView.trailingAnchor + .constraint(equalTo: trailingAnchor, constant: -8), + contentStackView.topAnchor + .constraint(equalTo: topAnchor, constant: 8), + contentStackView.bottomAnchor + .constraint(equalTo: bottomAnchor, constant: -8) + ]) + } +} diff --git a/ProjectManager/ProjectManager/View/ListViewController.swift b/ProjectManager/ProjectManager/View/ListViewController.swift index c5d4dd9c2..3146b8524 100644 --- a/ProjectManager/ProjectManager/View/ListViewController.swift +++ b/ProjectManager/ProjectManager/View/ListViewController.swift @@ -12,6 +12,7 @@ final class ListViewController: UIViewController { tableView.dataSource = self tableView.delegate = self tableView.backgroundColor = .orange + tableView.register(ListViewCell.self, forCellReuseIdentifier: "cell") return tableView }() @@ -59,7 +60,7 @@ extension ListViewController: UITableViewDataSource { } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - return UITableViewCell() + return ListViewCell() } } From e7f8af1a43f563e25ae947a96d1aaaafd9f9962b Mon Sep 17 00:00:00 2001 From: Moon <1tjen@naver.com> Date: Mon, 25 Sep 2023 09:07:34 +0900 Subject: [PATCH 06/48] =?UTF-8?q?feat:=20ListViewHeader=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=EB=B0=8F=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ProjectManager.xcodeproj/project.pbxproj | 4 + .../View/ListViewController.swift | 4 + .../ProjectManager/View/ListViewHeader.swift | 92 +++++++++++++++++++ 3 files changed, 100 insertions(+) create mode 100644 ProjectManager/ProjectManager/View/ListViewHeader.swift diff --git a/ProjectManager/ProjectManager.xcodeproj/project.pbxproj b/ProjectManager/ProjectManager.xcodeproj/project.pbxproj index 2fc007a64..2c3b2bde2 100644 --- a/ProjectManager/ProjectManager.xcodeproj/project.pbxproj +++ b/ProjectManager/ProjectManager.xcodeproj/project.pbxproj @@ -8,6 +8,7 @@ /* Begin PBXBuildFile section */ 28CA66E52AC0F72600FFD7A9 /* ListViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28CA66E42AC0F72600FFD7A9 /* ListViewCell.swift */; }; + 28CA66E72AC0FE3100FFD7A9 /* ListViewHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28CA66E62AC0FE3100FFD7A9 /* ListViewHeader.swift */; }; C7431F0625F51E1D0094C4CF /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7431F0525F51E1D0094C4CF /* AppDelegate.swift */; }; C7431F0825F51E1D0094C4CF /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7431F0725F51E1D0094C4CF /* SceneDelegate.swift */; }; C7431F0A25F51E1D0094C4CF /* ListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7431F0925F51E1D0094C4CF /* ListViewController.swift */; }; @@ -17,6 +18,7 @@ /* Begin PBXFileReference section */ 28CA66E42AC0F72600FFD7A9 /* ListViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListViewCell.swift; sourceTree = ""; }; + 28CA66E62AC0FE3100FFD7A9 /* ListViewHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListViewHeader.swift; sourceTree = ""; }; C7431F0225F51E1D0094C4CF /* ProjectManager.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ProjectManager.app; sourceTree = BUILT_PRODUCTS_DIR; }; C7431F0525F51E1D0094C4CF /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; C7431F0725F51E1D0094C4CF /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; @@ -49,6 +51,7 @@ children = ( C7431F1025F51E1E0094C4CF /* LaunchScreen.storyboard */, C7431F0925F51E1D0094C4CF /* ListViewController.swift */, + 28CA66E62AC0FE3100FFD7A9 /* ListViewHeader.swift */, 28CA66E42AC0F72600FFD7A9 /* ListViewCell.swift */, ); path = View; @@ -179,6 +182,7 @@ 28CA66E52AC0F72600FFD7A9 /* ListViewCell.swift in Sources */, C7431F0A25F51E1D0094C4CF /* ListViewController.swift in Sources */, C7431F0625F51E1D0094C4CF /* AppDelegate.swift in Sources */, + 28CA66E72AC0FE3100FFD7A9 /* ListViewHeader.swift in Sources */, C7431F0825F51E1D0094C4CF /* SceneDelegate.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/ProjectManager/ProjectManager/View/ListViewController.swift b/ProjectManager/ProjectManager/View/ListViewController.swift index 3146b8524..c942ccde3 100644 --- a/ProjectManager/ProjectManager/View/ListViewController.swift +++ b/ProjectManager/ProjectManager/View/ListViewController.swift @@ -55,6 +55,10 @@ extension ListViewController { // MARK: - Table View Data Source extension ListViewController: UITableViewDataSource { + func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { + return ListViewHeader() + } + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return 1 } diff --git a/ProjectManager/ProjectManager/View/ListViewHeader.swift b/ProjectManager/ProjectManager/View/ListViewHeader.swift new file mode 100644 index 000000000..f7c5ac0ee --- /dev/null +++ b/ProjectManager/ProjectManager/View/ListViewHeader.swift @@ -0,0 +1,92 @@ +// +// ListViewHeader.swift +// ProjectManager +// +// Created by Moon on 2023/09/25. +// + +import UIKit + +final class ListViewHeader: UIView { + private let titleLabel: UILabel = { + let label = UILabel() + label.font = UIFont.preferredFont(forTextStyle: .largeTitle) + label.text = "TODO" + + return label + }() + + private var contentAmountLabel: UILabel = { + let label = UILabel() + label.font = UIFont.preferredFont(forTextStyle: .title2) + label.text = "3" + label.textColor = .white + label.textAlignment = .center + label.layer.backgroundColor = UIColor.black.cgColor + label.layer.cornerRadius = 20 + + return label + }() + + private let contentStackView: UIStackView = { + let stackView = UIStackView() + stackView.axis = .horizontal + stackView.spacing = 8 + + return stackView + }() + + init() { + super.init(frame: .zero) + + configureUI() + backgroundColor = .cyan + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +// MARK: - Configure UI +extension ListViewHeader { + private func configureUI() { + addSubviews() + setUpConstraints() + } + + private func addSubviews() { + [titleLabel, contentAmountLabel].forEach { + contentStackView.addArrangedSubview($0) + } + + addSubview(contentStackView) + } + + private func setUpConstraints() { + setUpContentAmountLabelConstraints() + setUpContentStackViewConstraints() + } + + private func setUpContentAmountLabelConstraints() { + contentAmountLabel.translatesAutoresizingMaskIntoConstraints = false + + NSLayoutConstraint.activate([ + contentAmountLabel.widthAnchor + .constraint(equalTo: contentAmountLabel.heightAnchor, multiplier: 1) + ]) + } + + private func setUpContentStackViewConstraints() { + contentStackView.translatesAutoresizingMaskIntoConstraints = false + + NSLayoutConstraint.activate([ + contentStackView.leadingAnchor + .constraint(equalTo: leadingAnchor, constant: 8), + contentStackView.topAnchor + .constraint(equalTo: topAnchor, constant: 8), + contentStackView.bottomAnchor + .constraint(equalTo: bottomAnchor, constant: -8) + ]) + } +} From c3c89b7652a2e09ad86b4a915e01f6233d01011e Mon Sep 17 00:00:00 2001 From: Moon <1tjen@naver.com> Date: Mon, 25 Sep 2023 11:29:11 +0900 Subject: [PATCH 07/48] =?UTF-8?q?refactor:=20ListViewCell=EC=9D=98=20subVi?= =?UTF-8?q?ew=EB=93=A4=EC=9D=84=20contentView=EC=9D=98=20subView=EB=A1=9C?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ProjectManager/ProjectManager/View/ListViewCell.swift | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ProjectManager/ProjectManager/View/ListViewCell.swift b/ProjectManager/ProjectManager/View/ListViewCell.swift index 109c88cd0..fe1a3da4e 100644 --- a/ProjectManager/ProjectManager/View/ListViewCell.swift +++ b/ProjectManager/ProjectManager/View/ListViewCell.swift @@ -66,7 +66,7 @@ extension ListViewCell { contentStackView.addArrangedSubview($0) } - addSubview(contentStackView) + contentView.addSubview(contentStackView) } private func setUpContentStackViewConstraints() { @@ -74,13 +74,13 @@ extension ListViewCell { NSLayoutConstraint.activate([ contentStackView.leadingAnchor - .constraint(equalTo: leadingAnchor, constant: 8), + .constraint(equalTo: contentView.leadingAnchor, constant: 8), contentStackView.trailingAnchor - .constraint(equalTo: trailingAnchor, constant: -8), + .constraint(equalTo: contentView.trailingAnchor, constant: -8), contentStackView.topAnchor - .constraint(equalTo: topAnchor, constant: 8), + .constraint(equalTo: contentView.topAnchor, constant: 8), contentStackView.bottomAnchor - .constraint(equalTo: bottomAnchor, constant: -8) + .constraint(equalTo: contentView.bottomAnchor, constant: -8) ]) } } From d1eeddae68d1837ec4193509a52706bcb7b0ac14 Mon Sep 17 00:00:00 2001 From: Moon <1tjen@naver.com> Date: Mon, 25 Sep 2023 11:40:06 +0900 Subject: [PATCH 08/48] =?UTF-8?q?feat:=20ListViewController=EC=9D=98=20Tab?= =?UTF-8?q?leView=EC=97=90=20Cell=20=EC=82=AC=EC=9D=B4=20=EA=B0=84?= =?UTF-8?q?=EA=B2=A9=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ProjectManager/ProjectManager/View/ListViewCell.swift | 7 +++++++ .../ProjectManager/View/ListViewController.swift | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/ProjectManager/ProjectManager/View/ListViewCell.swift b/ProjectManager/ProjectManager/View/ListViewCell.swift index fe1a3da4e..bd46db623 100644 --- a/ProjectManager/ProjectManager/View/ListViewCell.swift +++ b/ProjectManager/ProjectManager/View/ListViewCell.swift @@ -47,11 +47,18 @@ final class ListViewCell: UITableViewCell { configureUI() backgroundColor = .brown + contentView.backgroundColor = .white } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } + + override func layoutSubviews() { + super.layoutSubviews() + + contentView.frame = contentView.frame.inset(by: UIEdgeInsets(top: 8, left: 0, bottom: 0, right: 0)) + } } // MARK: - Configure UI diff --git a/ProjectManager/ProjectManager/View/ListViewController.swift b/ProjectManager/ProjectManager/View/ListViewController.swift index c942ccde3..12efa52eb 100644 --- a/ProjectManager/ProjectManager/View/ListViewController.swift +++ b/ProjectManager/ProjectManager/View/ListViewController.swift @@ -60,7 +60,7 @@ extension ListViewController: UITableViewDataSource { } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return 1 + return 2 } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { From 6ee0f756b02efc88742c06e08028b99d4eccbaac Mon Sep 17 00:00:00 2001 From: Moon <1tjen@naver.com> Date: Mon, 25 Sep 2023 12:24:31 +0900 Subject: [PATCH 09/48] =?UTF-8?q?refactor:=20=EB=B7=B0=EB=93=A4=EC=9D=98?= =?UTF-8?q?=20=EB=B0=B0=EA=B2=BD=EC=83=89=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ProjectManager/View/ListViewCell.swift | 16 +++++++++++++--- .../ProjectManager/View/ListViewController.swift | 5 ++--- .../ProjectManager/View/ListViewHeader.swift | 1 - 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/ProjectManager/ProjectManager/View/ListViewCell.swift b/ProjectManager/ProjectManager/View/ListViewCell.swift index bd46db623..cf984bee4 100644 --- a/ProjectManager/ProjectManager/View/ListViewCell.swift +++ b/ProjectManager/ProjectManager/View/ListViewCell.swift @@ -46,8 +46,6 @@ final class ListViewCell: UITableViewCell { super.init(style: style, reuseIdentifier: reuseIdentifier) configureUI() - backgroundColor = .brown - contentView.backgroundColor = .white } required init?(coder: NSCoder) { @@ -57,7 +55,13 @@ final class ListViewCell: UITableViewCell { override func layoutSubviews() { super.layoutSubviews() - contentView.frame = contentView.frame.inset(by: UIEdgeInsets(top: 8, left: 0, bottom: 0, right: 0)) + contentView.frame = contentView.frame + .inset(by: UIEdgeInsets( + top: 8, + left: .zero, + bottom: .zero, + right: .zero) + ) } } @@ -66,6 +70,7 @@ extension ListViewCell { private func configureUI() { addSubviews() setUpContentStackViewConstraints() + setUpBackgroundColors() } private func addSubviews() { @@ -90,4 +95,9 @@ extension ListViewCell { .constraint(equalTo: contentView.bottomAnchor, constant: -8) ]) } + + private func setUpBackgroundColors() { + backgroundColor = .systemGray6 + contentView.backgroundColor = .systemBackground + } } diff --git a/ProjectManager/ProjectManager/View/ListViewController.swift b/ProjectManager/ProjectManager/View/ListViewController.swift index 12efa52eb..7a48ed87f 100644 --- a/ProjectManager/ProjectManager/View/ListViewController.swift +++ b/ProjectManager/ProjectManager/View/ListViewController.swift @@ -11,8 +11,9 @@ final class ListViewController: UIViewController { let tableView = UITableView() tableView.dataSource = self tableView.delegate = self - tableView.backgroundColor = .orange + tableView.backgroundColor = .systemGray6 tableView.register(ListViewCell.self, forCellReuseIdentifier: "cell") + tableView.separatorInset.left = .zero return tableView }() @@ -20,8 +21,6 @@ final class ListViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() - view.backgroundColor = .yellow - configureUI() } } diff --git a/ProjectManager/ProjectManager/View/ListViewHeader.swift b/ProjectManager/ProjectManager/View/ListViewHeader.swift index f7c5ac0ee..db030b08a 100644 --- a/ProjectManager/ProjectManager/View/ListViewHeader.swift +++ b/ProjectManager/ProjectManager/View/ListViewHeader.swift @@ -40,7 +40,6 @@ final class ListViewHeader: UIView { super.init(frame: .zero) configureUI() - backgroundColor = .cyan } required init?(coder: NSCoder) { From 35a26460bff545c528f5509c9ce0a6846a019360 Mon Sep 17 00:00:00 2001 From: Moon <1tjen@naver.com> Date: Mon, 25 Sep 2023 17:49:50 +0900 Subject: [PATCH 10/48] =?UTF-8?q?refactor:=20ListViewCell=EC=9D=98=20deadl?= =?UTF-8?q?ineLabel=20=EB=84=A4=EC=9D=B4=EB=B0=8D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ProjectManager/ProjectManager/View/ListViewCell.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ProjectManager/ProjectManager/View/ListViewCell.swift b/ProjectManager/ProjectManager/View/ListViewCell.swift index cf984bee4..2c0272b79 100644 --- a/ProjectManager/ProjectManager/View/ListViewCell.swift +++ b/ProjectManager/ProjectManager/View/ListViewCell.swift @@ -26,7 +26,7 @@ final class ListViewCell: UITableViewCell { return label }() - private let dateLabel: UILabel = { + private let deadlineLabel: UILabel = { let label = UILabel() label.font = UIFont.preferredFont(forTextStyle: .callout) label.text = "2023. 09. 25." @@ -74,7 +74,7 @@ extension ListViewCell { } private func addSubviews() { - [titleLabel, descriptionLabel, dateLabel].forEach { + [titleLabel, descriptionLabel, deadlineLabel].forEach { contentStackView.addArrangedSubview($0) } From 9f35f8e010219812437d9d1fb9ffb7156cc77a6d Mon Sep 17 00:00:00 2001 From: Moon <1tjen@naver.com> Date: Mon, 25 Sep 2023 17:50:49 +0900 Subject: [PATCH 11/48] =?UTF-8?q?feat:=20Todo=20=EB=AA=A8=EB=8D=B8=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ProjectManager.xcodeproj/project.pbxproj | 4 ++++ ProjectManager/ProjectManager/Model/Todo.swift | 14 ++++++++++++++ 2 files changed, 18 insertions(+) create mode 100644 ProjectManager/ProjectManager/Model/Todo.swift diff --git a/ProjectManager/ProjectManager.xcodeproj/project.pbxproj b/ProjectManager/ProjectManager.xcodeproj/project.pbxproj index 2c3b2bde2..ecd847423 100644 --- a/ProjectManager/ProjectManager.xcodeproj/project.pbxproj +++ b/ProjectManager/ProjectManager.xcodeproj/project.pbxproj @@ -9,6 +9,7 @@ /* Begin PBXBuildFile section */ 28CA66E52AC0F72600FFD7A9 /* ListViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28CA66E42AC0F72600FFD7A9 /* ListViewCell.swift */; }; 28CA66E72AC0FE3100FFD7A9 /* ListViewHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28CA66E62AC0FE3100FFD7A9 /* ListViewHeader.swift */; }; + 28CA66ED2AC1808E00FFD7A9 /* Todo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28CA66EC2AC1808E00FFD7A9 /* Todo.swift */; }; C7431F0625F51E1D0094C4CF /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7431F0525F51E1D0094C4CF /* AppDelegate.swift */; }; C7431F0825F51E1D0094C4CF /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7431F0725F51E1D0094C4CF /* SceneDelegate.swift */; }; C7431F0A25F51E1D0094C4CF /* ListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7431F0925F51E1D0094C4CF /* ListViewController.swift */; }; @@ -19,6 +20,7 @@ /* Begin PBXFileReference section */ 28CA66E42AC0F72600FFD7A9 /* ListViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListViewCell.swift; sourceTree = ""; }; 28CA66E62AC0FE3100FFD7A9 /* ListViewHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListViewHeader.swift; sourceTree = ""; }; + 28CA66EC2AC1808E00FFD7A9 /* Todo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Todo.swift; sourceTree = ""; }; C7431F0225F51E1D0094C4CF /* ProjectManager.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ProjectManager.app; sourceTree = BUILT_PRODUCTS_DIR; }; C7431F0525F51E1D0094C4CF /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; C7431F0725F51E1D0094C4CF /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; @@ -60,6 +62,7 @@ 2897D3772AC05F9500662BAA /* Model */ = { isa = PBXGroup; children = ( + 28CA66EC2AC1808E00FFD7A9 /* Todo.swift */, ); path = Model; sourceTree = ""; @@ -179,6 +182,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 28CA66ED2AC1808E00FFD7A9 /* Todo.swift in Sources */, 28CA66E52AC0F72600FFD7A9 /* ListViewCell.swift in Sources */, C7431F0A25F51E1D0094C4CF /* ListViewController.swift in Sources */, C7431F0625F51E1D0094C4CF /* AppDelegate.swift in Sources */, diff --git a/ProjectManager/ProjectManager/Model/Todo.swift b/ProjectManager/ProjectManager/Model/Todo.swift new file mode 100644 index 000000000..11a158901 --- /dev/null +++ b/ProjectManager/ProjectManager/Model/Todo.swift @@ -0,0 +1,14 @@ +// +// Todo.swift +// ProjectManager +// +// Created by Moon on 2023/09/25. +// + +import Foundation + +struct Todo { + var title: String + var description: String + var deadline: Date +} From 2ca8a153081b418c23dbabd20ec125050ebda072 Mon Sep 17 00:00:00 2001 From: Moon <1tjen@naver.com> Date: Mon, 25 Sep 2023 18:09:23 +0900 Subject: [PATCH 12/48] =?UTF-8?q?feat:=20ListViewModel=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ProjectManager.xcodeproj/project.pbxproj | 4 ++++ .../View/ListViewController.swift | 2 ++ .../ViewModel/ListViewModel.swift | 23 +++++++++++++++++++ 3 files changed, 29 insertions(+) create mode 100644 ProjectManager/ProjectManager/ViewModel/ListViewModel.swift diff --git a/ProjectManager/ProjectManager.xcodeproj/project.pbxproj b/ProjectManager/ProjectManager.xcodeproj/project.pbxproj index ecd847423..edade322a 100644 --- a/ProjectManager/ProjectManager.xcodeproj/project.pbxproj +++ b/ProjectManager/ProjectManager.xcodeproj/project.pbxproj @@ -9,6 +9,7 @@ /* Begin PBXBuildFile section */ 28CA66E52AC0F72600FFD7A9 /* ListViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28CA66E42AC0F72600FFD7A9 /* ListViewCell.swift */; }; 28CA66E72AC0FE3100FFD7A9 /* ListViewHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28CA66E62AC0FE3100FFD7A9 /* ListViewHeader.swift */; }; + 28CA66EB2AC1805300FFD7A9 /* ListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28CA66EA2AC1805300FFD7A9 /* ListViewModel.swift */; }; 28CA66ED2AC1808E00FFD7A9 /* Todo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28CA66EC2AC1808E00FFD7A9 /* Todo.swift */; }; C7431F0625F51E1D0094C4CF /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7431F0525F51E1D0094C4CF /* AppDelegate.swift */; }; C7431F0825F51E1D0094C4CF /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7431F0725F51E1D0094C4CF /* SceneDelegate.swift */; }; @@ -20,6 +21,7 @@ /* Begin PBXFileReference section */ 28CA66E42AC0F72600FFD7A9 /* ListViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListViewCell.swift; sourceTree = ""; }; 28CA66E62AC0FE3100FFD7A9 /* ListViewHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListViewHeader.swift; sourceTree = ""; }; + 28CA66EA2AC1805300FFD7A9 /* ListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListViewModel.swift; sourceTree = ""; }; 28CA66EC2AC1808E00FFD7A9 /* Todo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Todo.swift; sourceTree = ""; }; C7431F0225F51E1D0094C4CF /* ProjectManager.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ProjectManager.app; sourceTree = BUILT_PRODUCTS_DIR; }; C7431F0525F51E1D0094C4CF /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; @@ -44,6 +46,7 @@ 2897D3752AC05F8400662BAA /* ViewModel */ = { isa = PBXGroup; children = ( + 28CA66EA2AC1805300FFD7A9 /* ListViewModel.swift */, ); path = ViewModel; sourceTree = ""; @@ -185,6 +188,7 @@ 28CA66ED2AC1808E00FFD7A9 /* Todo.swift in Sources */, 28CA66E52AC0F72600FFD7A9 /* ListViewCell.swift in Sources */, C7431F0A25F51E1D0094C4CF /* ListViewController.swift in Sources */, + 28CA66EB2AC1805300FFD7A9 /* ListViewModel.swift in Sources */, C7431F0625F51E1D0094C4CF /* AppDelegate.swift in Sources */, 28CA66E72AC0FE3100FFD7A9 /* ListViewHeader.swift in Sources */, C7431F0825F51E1D0094C4CF /* SceneDelegate.swift in Sources */, diff --git a/ProjectManager/ProjectManager/View/ListViewController.swift b/ProjectManager/ProjectManager/View/ListViewController.swift index 7a48ed87f..1733557f7 100644 --- a/ProjectManager/ProjectManager/View/ListViewController.swift +++ b/ProjectManager/ProjectManager/View/ListViewController.swift @@ -18,6 +18,8 @@ final class ListViewController: UIViewController { return tableView }() + let listViewModel = ListViewModel() + override func viewDidLoad() { super.viewDidLoad() diff --git a/ProjectManager/ProjectManager/ViewModel/ListViewModel.swift b/ProjectManager/ProjectManager/ViewModel/ListViewModel.swift new file mode 100644 index 000000000..4c9d625eb --- /dev/null +++ b/ProjectManager/ProjectManager/ViewModel/ListViewModel.swift @@ -0,0 +1,23 @@ +// +// ListViewModel.swift +// ProjectManager +// +// Created by Moon on 2023/09/25. +// + +import Foundation + +final class ListViewModel { + private let todoList = [ + Todo(title: "1", description: "111", deadline: Date()), + Todo(title: "2", description: "222", deadline: Date()), + Todo(title: "3", description: "333", deadline: Date()), + Todo(title: "4", description: "444", deadline: Date()), + Todo(title: "5", description: "555", deadline: Date()), + Todo(title: "6", description: "666", deadline: Date()) + ] + + func getTodoList() -> [Todo] { + return todoList + } +} From eadf176d1419b6d0deb84935c3c007103dd52b92 Mon Sep 17 00:00:00 2001 From: Moon <1tjen@naver.com> Date: Mon, 25 Sep 2023 18:11:29 +0900 Subject: [PATCH 13/48] =?UTF-8?q?refactor:=20ListHeader,=20ListCell=20?= =?UTF-8?q?=EB=84=A4=EC=9D=B4=EB=B0=8D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ProjectManager.xcodeproj/project.pbxproj | 16 ++++++++-------- .../View/{ListViewCell.swift => ListCell.swift} | 6 +++--- .../{ListViewHeader.swift => ListHeader.swift} | 6 +++--- .../ProjectManager/View/ListViewController.swift | 6 +++--- 4 files changed, 17 insertions(+), 17 deletions(-) rename ProjectManager/ProjectManager/View/{ListViewCell.swift => ListCell.swift} (96%) rename ProjectManager/ProjectManager/View/{ListViewHeader.swift => ListHeader.swift} (96%) diff --git a/ProjectManager/ProjectManager.xcodeproj/project.pbxproj b/ProjectManager/ProjectManager.xcodeproj/project.pbxproj index edade322a..e1824bfb0 100644 --- a/ProjectManager/ProjectManager.xcodeproj/project.pbxproj +++ b/ProjectManager/ProjectManager.xcodeproj/project.pbxproj @@ -7,8 +7,8 @@ objects = { /* Begin PBXBuildFile section */ - 28CA66E52AC0F72600FFD7A9 /* ListViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28CA66E42AC0F72600FFD7A9 /* ListViewCell.swift */; }; - 28CA66E72AC0FE3100FFD7A9 /* ListViewHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28CA66E62AC0FE3100FFD7A9 /* ListViewHeader.swift */; }; + 28CA66E52AC0F72600FFD7A9 /* ListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28CA66E42AC0F72600FFD7A9 /* ListCell.swift */; }; + 28CA66E72AC0FE3100FFD7A9 /* ListHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28CA66E62AC0FE3100FFD7A9 /* ListHeader.swift */; }; 28CA66EB2AC1805300FFD7A9 /* ListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28CA66EA2AC1805300FFD7A9 /* ListViewModel.swift */; }; 28CA66ED2AC1808E00FFD7A9 /* Todo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28CA66EC2AC1808E00FFD7A9 /* Todo.swift */; }; C7431F0625F51E1D0094C4CF /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7431F0525F51E1D0094C4CF /* AppDelegate.swift */; }; @@ -19,8 +19,8 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ - 28CA66E42AC0F72600FFD7A9 /* ListViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListViewCell.swift; sourceTree = ""; }; - 28CA66E62AC0FE3100FFD7A9 /* ListViewHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListViewHeader.swift; sourceTree = ""; }; + 28CA66E42AC0F72600FFD7A9 /* ListCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListCell.swift; sourceTree = ""; }; + 28CA66E62AC0FE3100FFD7A9 /* ListHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListHeader.swift; sourceTree = ""; }; 28CA66EA2AC1805300FFD7A9 /* ListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListViewModel.swift; sourceTree = ""; }; 28CA66EC2AC1808E00FFD7A9 /* Todo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Todo.swift; sourceTree = ""; }; C7431F0225F51E1D0094C4CF /* ProjectManager.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ProjectManager.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -56,8 +56,8 @@ children = ( C7431F1025F51E1E0094C4CF /* LaunchScreen.storyboard */, C7431F0925F51E1D0094C4CF /* ListViewController.swift */, - 28CA66E62AC0FE3100FFD7A9 /* ListViewHeader.swift */, - 28CA66E42AC0F72600FFD7A9 /* ListViewCell.swift */, + 28CA66E62AC0FE3100FFD7A9 /* ListHeader.swift */, + 28CA66E42AC0F72600FFD7A9 /* ListCell.swift */, ); path = View; sourceTree = ""; @@ -186,11 +186,11 @@ buildActionMask = 2147483647; files = ( 28CA66ED2AC1808E00FFD7A9 /* Todo.swift in Sources */, - 28CA66E52AC0F72600FFD7A9 /* ListViewCell.swift in Sources */, + 28CA66E52AC0F72600FFD7A9 /* ListCell.swift in Sources */, C7431F0A25F51E1D0094C4CF /* ListViewController.swift in Sources */, 28CA66EB2AC1805300FFD7A9 /* ListViewModel.swift in Sources */, C7431F0625F51E1D0094C4CF /* AppDelegate.swift in Sources */, - 28CA66E72AC0FE3100FFD7A9 /* ListViewHeader.swift in Sources */, + 28CA66E72AC0FE3100FFD7A9 /* ListHeader.swift in Sources */, C7431F0825F51E1D0094C4CF /* SceneDelegate.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/ProjectManager/ProjectManager/View/ListViewCell.swift b/ProjectManager/ProjectManager/View/ListCell.swift similarity index 96% rename from ProjectManager/ProjectManager/View/ListViewCell.swift rename to ProjectManager/ProjectManager/View/ListCell.swift index 2c0272b79..60355d484 100644 --- a/ProjectManager/ProjectManager/View/ListViewCell.swift +++ b/ProjectManager/ProjectManager/View/ListCell.swift @@ -1,5 +1,5 @@ // -// ListViewCell.swift +// ListCell.swift // ProjectManager // // Created by Moon on 2023/09/25. @@ -7,7 +7,7 @@ import UIKit -final class ListViewCell: UITableViewCell { +final class ListCell: UITableViewCell { private let titleLabel: UILabel = { let label = UILabel() label.font = UIFont.preferredFont(forTextStyle: .title3) @@ -66,7 +66,7 @@ final class ListViewCell: UITableViewCell { } // MARK: - Configure UI -extension ListViewCell { +extension ListCell { private func configureUI() { addSubviews() setUpContentStackViewConstraints() diff --git a/ProjectManager/ProjectManager/View/ListViewHeader.swift b/ProjectManager/ProjectManager/View/ListHeader.swift similarity index 96% rename from ProjectManager/ProjectManager/View/ListViewHeader.swift rename to ProjectManager/ProjectManager/View/ListHeader.swift index db030b08a..cd5cd5fe4 100644 --- a/ProjectManager/ProjectManager/View/ListViewHeader.swift +++ b/ProjectManager/ProjectManager/View/ListHeader.swift @@ -1,5 +1,5 @@ // -// ListViewHeader.swift +// ListHeader.swift // ProjectManager // // Created by Moon on 2023/09/25. @@ -7,7 +7,7 @@ import UIKit -final class ListViewHeader: UIView { +final class ListHeader: UIView { private let titleLabel: UILabel = { let label = UILabel() label.font = UIFont.preferredFont(forTextStyle: .largeTitle) @@ -48,7 +48,7 @@ final class ListViewHeader: UIView { } // MARK: - Configure UI -extension ListViewHeader { +extension ListHeader { private func configureUI() { addSubviews() setUpConstraints() diff --git a/ProjectManager/ProjectManager/View/ListViewController.swift b/ProjectManager/ProjectManager/View/ListViewController.swift index 1733557f7..58e43fd21 100644 --- a/ProjectManager/ProjectManager/View/ListViewController.swift +++ b/ProjectManager/ProjectManager/View/ListViewController.swift @@ -12,7 +12,7 @@ final class ListViewController: UIViewController { tableView.dataSource = self tableView.delegate = self tableView.backgroundColor = .systemGray6 - tableView.register(ListViewCell.self, forCellReuseIdentifier: "cell") + tableView.register(ListCell.self, forCellReuseIdentifier: "cell") tableView.separatorInset.left = .zero return tableView @@ -57,7 +57,7 @@ extension ListViewController { // MARK: - Table View Data Source extension ListViewController: UITableViewDataSource { func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { - return ListViewHeader() + return ListHeader() } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { @@ -65,7 +65,7 @@ extension ListViewController: UITableViewDataSource { } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - return ListViewCell() + return ListCell() } } From 0941e915b25ed23e4ba8cebbf31be9d3a19b4356 Mon Sep 17 00:00:00 2001 From: Moon <1tjen@naver.com> Date: Mon, 25 Sep 2023 20:13:21 +0900 Subject: [PATCH 14/48] =?UTF-8?q?feat:=20identifier=EB=A5=BC=20=EC=9C=84?= =?UTF-8?q?=ED=95=9C=20Reusable=20=ED=94=84=EB=A1=9C=ED=86=A0=EC=BD=9C=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ProjectManager.xcodeproj/project.pbxproj | 12 ++++++++++++ .../ProjectManager/Utility/Reusable.swift | 18 ++++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 ProjectManager/ProjectManager/Utility/Reusable.swift diff --git a/ProjectManager/ProjectManager.xcodeproj/project.pbxproj b/ProjectManager/ProjectManager.xcodeproj/project.pbxproj index e1824bfb0..909c260ec 100644 --- a/ProjectManager/ProjectManager.xcodeproj/project.pbxproj +++ b/ProjectManager/ProjectManager.xcodeproj/project.pbxproj @@ -11,6 +11,7 @@ 28CA66E72AC0FE3100FFD7A9 /* ListHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28CA66E62AC0FE3100FFD7A9 /* ListHeader.swift */; }; 28CA66EB2AC1805300FFD7A9 /* ListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28CA66EA2AC1805300FFD7A9 /* ListViewModel.swift */; }; 28CA66ED2AC1808E00FFD7A9 /* Todo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28CA66EC2AC1808E00FFD7A9 /* Todo.swift */; }; + 28CA66F22AC1A08600FFD7A9 /* Reusable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28CA66F12AC1A08600FFD7A9 /* Reusable.swift */; }; C7431F0625F51E1D0094C4CF /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7431F0525F51E1D0094C4CF /* AppDelegate.swift */; }; C7431F0825F51E1D0094C4CF /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7431F0725F51E1D0094C4CF /* SceneDelegate.swift */; }; C7431F0A25F51E1D0094C4CF /* ListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7431F0925F51E1D0094C4CF /* ListViewController.swift */; }; @@ -23,6 +24,7 @@ 28CA66E62AC0FE3100FFD7A9 /* ListHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListHeader.swift; sourceTree = ""; }; 28CA66EA2AC1805300FFD7A9 /* ListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListViewModel.swift; sourceTree = ""; }; 28CA66EC2AC1808E00FFD7A9 /* Todo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Todo.swift; sourceTree = ""; }; + 28CA66F12AC1A08600FFD7A9 /* Reusable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Reusable.swift; sourceTree = ""; }; C7431F0225F51E1D0094C4CF /* ProjectManager.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ProjectManager.app; sourceTree = BUILT_PRODUCTS_DIR; }; C7431F0525F51E1D0094C4CF /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; C7431F0725F51E1D0094C4CF /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; @@ -87,6 +89,14 @@ path = Resource; sourceTree = ""; }; + 28CA66F02AC1A02C00FFD7A9 /* Utility */ = { + isa = PBXGroup; + children = ( + 28CA66F12AC1A08600FFD7A9 /* Reusable.swift */, + ); + path = Utility; + sourceTree = ""; + }; C7431EF925F51E1D0094C4CF = { isa = PBXGroup; children = ( @@ -111,6 +121,7 @@ 2897D3762AC05F8D00662BAA /* View */, 2897D3752AC05F8400662BAA /* ViewModel */, 2897D3792AC05FBE00662BAA /* Resource */, + 28CA66F02AC1A02C00FFD7A9 /* Utility */, C7431F1325F51E1E0094C4CF /* Info.plist */, ); path = ProjectManager; @@ -187,6 +198,7 @@ files = ( 28CA66ED2AC1808E00FFD7A9 /* Todo.swift in Sources */, 28CA66E52AC0F72600FFD7A9 /* ListCell.swift in Sources */, + 28CA66F22AC1A08600FFD7A9 /* Reusable.swift in Sources */, C7431F0A25F51E1D0094C4CF /* ListViewController.swift in Sources */, 28CA66EB2AC1805300FFD7A9 /* ListViewModel.swift in Sources */, C7431F0625F51E1D0094C4CF /* AppDelegate.swift in Sources */, diff --git a/ProjectManager/ProjectManager/Utility/Reusable.swift b/ProjectManager/ProjectManager/Utility/Reusable.swift new file mode 100644 index 000000000..c7838958a --- /dev/null +++ b/ProjectManager/ProjectManager/Utility/Reusable.swift @@ -0,0 +1,18 @@ +// +// Reusable.swift +// ProjectManager +// +// Created by Moon on 2023/09/25. +// + +import UIKit + +protocol Reusable { } + +extension Reusable where Self: UIView { + static var identifier: String { + return String(describing: self) + } +} + +extension UITableViewCell: Reusable { } From d2fdb28850a4ad8bd93d9d0795f55ff5fa6dd08e Mon Sep 17 00:00:00 2001 From: Moon <1tjen@naver.com> Date: Mon, 25 Sep 2023 20:37:06 +0900 Subject: [PATCH 15/48] =?UTF-8?q?feat:=20ListCellViewModel=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ProjectManager.xcodeproj/project.pbxproj | 4 ++ .../ViewModel/ListCellViewModel.swift | 70 +++++++++++++++++++ 2 files changed, 74 insertions(+) create mode 100644 ProjectManager/ProjectManager/ViewModel/ListCellViewModel.swift diff --git a/ProjectManager/ProjectManager.xcodeproj/project.pbxproj b/ProjectManager/ProjectManager.xcodeproj/project.pbxproj index 909c260ec..98e8cdf43 100644 --- a/ProjectManager/ProjectManager.xcodeproj/project.pbxproj +++ b/ProjectManager/ProjectManager.xcodeproj/project.pbxproj @@ -11,6 +11,7 @@ 28CA66E72AC0FE3100FFD7A9 /* ListHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28CA66E62AC0FE3100FFD7A9 /* ListHeader.swift */; }; 28CA66EB2AC1805300FFD7A9 /* ListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28CA66EA2AC1805300FFD7A9 /* ListViewModel.swift */; }; 28CA66ED2AC1808E00FFD7A9 /* Todo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28CA66EC2AC1808E00FFD7A9 /* Todo.swift */; }; + 28CA66EF2AC1865000FFD7A9 /* ListCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28CA66EE2AC1865000FFD7A9 /* ListCellViewModel.swift */; }; 28CA66F22AC1A08600FFD7A9 /* Reusable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28CA66F12AC1A08600FFD7A9 /* Reusable.swift */; }; C7431F0625F51E1D0094C4CF /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7431F0525F51E1D0094C4CF /* AppDelegate.swift */; }; C7431F0825F51E1D0094C4CF /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7431F0725F51E1D0094C4CF /* SceneDelegate.swift */; }; @@ -24,6 +25,7 @@ 28CA66E62AC0FE3100FFD7A9 /* ListHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListHeader.swift; sourceTree = ""; }; 28CA66EA2AC1805300FFD7A9 /* ListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListViewModel.swift; sourceTree = ""; }; 28CA66EC2AC1808E00FFD7A9 /* Todo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Todo.swift; sourceTree = ""; }; + 28CA66EE2AC1865000FFD7A9 /* ListCellViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListCellViewModel.swift; sourceTree = ""; }; 28CA66F12AC1A08600FFD7A9 /* Reusable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Reusable.swift; sourceTree = ""; }; C7431F0225F51E1D0094C4CF /* ProjectManager.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ProjectManager.app; sourceTree = BUILT_PRODUCTS_DIR; }; C7431F0525F51E1D0094C4CF /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; @@ -49,6 +51,7 @@ isa = PBXGroup; children = ( 28CA66EA2AC1805300FFD7A9 /* ListViewModel.swift */, + 28CA66EE2AC1865000FFD7A9 /* ListCellViewModel.swift */, ); path = ViewModel; sourceTree = ""; @@ -198,6 +201,7 @@ files = ( 28CA66ED2AC1808E00FFD7A9 /* Todo.swift in Sources */, 28CA66E52AC0F72600FFD7A9 /* ListCell.swift in Sources */, + 28CA66EF2AC1865000FFD7A9 /* ListCellViewModel.swift in Sources */, 28CA66F22AC1A08600FFD7A9 /* Reusable.swift in Sources */, C7431F0A25F51E1D0094C4CF /* ListViewController.swift in Sources */, 28CA66EB2AC1805300FFD7A9 /* ListViewModel.swift in Sources */, diff --git a/ProjectManager/ProjectManager/ViewModel/ListCellViewModel.swift b/ProjectManager/ProjectManager/ViewModel/ListCellViewModel.swift new file mode 100644 index 000000000..ea6c6c196 --- /dev/null +++ b/ProjectManager/ProjectManager/ViewModel/ListCellViewModel.swift @@ -0,0 +1,70 @@ +// +// ListCellViewModel.swift +// ProjectManager +// +// Created by Moon on 2023/09/25. +// + +import Foundation + +final class ListCellViewModel { + // 바인딩을 위한 클로저 타입 + typealias bind = ((ListCellViewModel) -> Void) + + // 바인딩할 데이터를 가지고 있는 모델 + private let todo: Todo + + // 바인딩에 사용할 변수들 + var title: String { + didSet { + bindTitle?(self) + } + } + + var description: String { + didSet { + bindDescription?(self) + } + } + + var deadline: String { + didSet { + bindDeadline?(self) + } + } + + // ListCell에서 각각의 레이블을 바인딩하기 위한 클로저들 + private var bindTitle: bind? + private var bindDescription: bind? + private var bindDeadline: bind? + + // deadline 바인딩을 위한 포맷팅용 + private let dateFormatter: DateFormatter = { + let formatter = DateFormatter() + formatter.dateFormat = "yyyy. M. d." + + return formatter + }() + + init(todo: Todo) { + self.todo = todo + title = todo.title + description = todo.description + deadline = dateFormatter.string(from: todo.deadline) + } + + func bindTitle(_ handler: @escaping bind) { + handler(self) + bindTitle = handler + } + + func bindDescription(_ handler: @escaping bind) { + handler(self) + bindDescription = handler + } + + func bindDeadline(_ handler: @escaping bind) { + handler(self) + bindDeadline = handler + } +} From 5b4b6ec3999d3dc00f9e3cb01166a35efd8f0047 Mon Sep 17 00:00:00 2001 From: Moon <1tjen@naver.com> Date: Mon, 25 Sep 2023 20:40:41 +0900 Subject: [PATCH 16/48] =?UTF-8?q?refactor:=20ListHeader=EC=99=80=20?= =?UTF-8?q?=EB=B0=94=EC=9D=B8=EB=94=A9=ED=95=A0=20=EC=88=98=20=EC=9E=88?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - todoList가 변경되면 ListHeader와 바인딩할 count가 바뀔 수 있도록 구현 - ListHeader와 바인딩할 count 변수 생성 --- .../ViewModel/ListViewModel.swift | 50 +++++++++++++++---- 1 file changed, 40 insertions(+), 10 deletions(-) diff --git a/ProjectManager/ProjectManager/ViewModel/ListViewModel.swift b/ProjectManager/ProjectManager/ViewModel/ListViewModel.swift index 4c9d625eb..f2fa17265 100644 --- a/ProjectManager/ProjectManager/ViewModel/ListViewModel.swift +++ b/ProjectManager/ProjectManager/ViewModel/ListViewModel.swift @@ -8,16 +8,46 @@ import Foundation final class ListViewModel { - private let todoList = [ - Todo(title: "1", description: "111", deadline: Date()), - Todo(title: "2", description: "222", deadline: Date()), - Todo(title: "3", description: "333", deadline: Date()), - Todo(title: "4", description: "444", deadline: Date()), - Todo(title: "5", description: "555", deadline: Date()), - Todo(title: "6", description: "666", deadline: Date()) - ] + // ListViewController에서 ListCell의 ListCellViewModel로 전달하게될 모델의 배열 + private var todoList: [Todo] { + didSet { + count = String(todoList.count) + } + } + + // ListHeader의 contentAmountLabel와 바인딩할 변수 + var count: String { + didSet { + bindCount?(self) + } + } + // ListHeader의 contentAmountLabel와 바인딩하기 위한 클로저 + private var bindCount: ((ListViewModel) -> Void)? + + init() { + todoList = [ + Todo(title: "1", description: "111", deadline: Date()), + Todo(title: "2", description: "222", deadline: Date()), + Todo(title: "3", description: "333", deadline: Date()), + Todo(title: "4", description: "444", deadline: Date()), + Todo(title: "5", description: "555", deadline: Date()), + Todo(title: "6", description: "666", deadline: Date()) + ] + + count = String(todoList.count) + } + + // ListViewController에서 ListCell의 ListCellViewModel로 전달하기 위해 인덱스로 찾아 리턴 + func getTodo(index: Int) -> Todo { + return todoList[index] + } + + func countTodo() -> Int { + return todoList.count + } - func getTodoList() -> [Todo] { - return todoList + func bindCount(_ handler: @escaping (ListViewModel) -> Void) { + handler(self) + bindCount = handler } } From 5b4b5cd206b24b5583c9c130821cb401eb840bf0 Mon Sep 17 00:00:00 2001 From: Moon <1tjen@naver.com> Date: Mon, 25 Sep 2023 20:41:33 +0900 Subject: [PATCH 17/48] =?UTF-8?q?feat:=20ListCell=EA=B3=BC=20ListCellViewM?= =?UTF-8?q?odel=20=EB=B0=94=EC=9D=B8=EB=94=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ProjectManager/View/ListCell.swift | 30 +++++++++++++++++-- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/ProjectManager/ProjectManager/View/ListCell.swift b/ProjectManager/ProjectManager/View/ListCell.swift index 60355d484..91a4b2d04 100644 --- a/ProjectManager/ProjectManager/View/ListCell.swift +++ b/ProjectManager/ProjectManager/View/ListCell.swift @@ -11,7 +11,6 @@ final class ListCell: UITableViewCell { private let titleLabel: UILabel = { let label = UILabel() label.font = UIFont.preferredFont(forTextStyle: .title3) - label.text = "책상정리" return label }() @@ -20,7 +19,6 @@ final class ListCell: UITableViewCell { let label = UILabel() label.font = UIFont.preferredFont(forTextStyle: .body) label.textColor = .systemGray3 - label.text = "책상정리..." label.numberOfLines = 3 return label @@ -29,7 +27,6 @@ final class ListCell: UITableViewCell { private let deadlineLabel: UILabel = { let label = UILabel() label.font = UIFont.preferredFont(forTextStyle: .callout) - label.text = "2023. 09. 25." return label }() @@ -42,6 +39,13 @@ final class ListCell: UITableViewCell { return stackView }() + // ListCell이 재사용 될 때마다 setUpViewModel을 통해 값이 들어오면 바인딩 함 + private var listCellViewModel: ListCellViewModel? { + didSet { + setUpBindings() + } + } + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) @@ -63,6 +67,26 @@ final class ListCell: UITableViewCell { right: .zero) ) } + + // ListCell이 재사용 될 때 ListCellViewModel을 받아옴 + func setUpViewModel(_ viewModel: ListCellViewModel) { + self.listCellViewModel = viewModel + } + + // viewModel을 받을 때마다 바인딩할 것 + private func setUpBindings() { + listCellViewModel?.bindTitle { [weak self] viewModel in + self?.titleLabel.text = viewModel.title + } + + listCellViewModel?.bindDescription { [weak self] viewModel in + self?.descriptionLabel.text = viewModel.description + } + + listCellViewModel?.bindDeadline { [weak self] viewModel in + self?.deadlineLabel.text = viewModel.deadline + } + } } // MARK: - Configure UI From 15cc06c718d0a882baaec1f091adfabb1fd005eb Mon Sep 17 00:00:00 2001 From: Moon <1tjen@naver.com> Date: Mon, 25 Sep 2023 20:41:57 +0900 Subject: [PATCH 18/48] =?UTF-8?q?feat:=20ListHeader=EC=99=80=20ListViewMod?= =?UTF-8?q?el=20=EB=B0=94=EC=9D=B8=EB=94=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ProjectManager/View/ListHeader.swift | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/ProjectManager/ProjectManager/View/ListHeader.swift b/ProjectManager/ProjectManager/View/ListHeader.swift index cd5cd5fe4..27d4f9424 100644 --- a/ProjectManager/ProjectManager/View/ListHeader.swift +++ b/ProjectManager/ProjectManager/View/ListHeader.swift @@ -19,7 +19,6 @@ final class ListHeader: UIView { private var contentAmountLabel: UILabel = { let label = UILabel() label.font = UIFont.preferredFont(forTextStyle: .title2) - label.text = "3" label.textColor = .white label.textAlignment = .center label.layer.backgroundColor = UIColor.black.cgColor @@ -36,15 +35,28 @@ final class ListHeader: UIView { return stackView }() - init() { + // contentAmountLabel에 TODO 개수를 표시하기 위한 데이터를 바인딩 할 뷰 모델 + private let listViewModel: ListViewModel + + init(viewModel: ListViewModel) { + listViewModel = viewModel + super.init(frame: .zero) configureUI() + setUpBindings() } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } + + // contentAmountLabel에 TODO 개수 표시 + private func setUpBindings() { + listViewModel.bindCount { [weak self] viewModel in + self?.contentAmountLabel.text = viewModel.count + } + } } // MARK: - Configure UI From 76e9c4fa669b95cd8be24db57346ac27d9a23b5d Mon Sep 17 00:00:00 2001 From: Moon <1tjen@naver.com> Date: Mon, 25 Sep 2023 20:45:32 +0900 Subject: [PATCH 19/48] =?UTF-8?q?refactor:=20ListViewController=EC=97=90?= =?UTF-8?q?=EC=84=9C=20ViewModel=EC=9D=84=20=EC=82=AC=EC=9A=A9=ED=95=A0=20?= =?UTF-8?q?=EC=88=98=20=EC=9E=88=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../View/ListViewController.swift | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/ProjectManager/ProjectManager/View/ListViewController.swift b/ProjectManager/ProjectManager/View/ListViewController.swift index 58e43fd21..13b4943a9 100644 --- a/ProjectManager/ProjectManager/View/ListViewController.swift +++ b/ProjectManager/ProjectManager/View/ListViewController.swift @@ -12,13 +12,13 @@ final class ListViewController: UIViewController { tableView.dataSource = self tableView.delegate = self tableView.backgroundColor = .systemGray6 - tableView.register(ListCell.self, forCellReuseIdentifier: "cell") + tableView.register(ListCell.self, forCellReuseIdentifier: ListCell.identifier) tableView.separatorInset.left = .zero return tableView }() - let listViewModel = ListViewModel() + private let listViewModel = ListViewModel() override func viewDidLoad() { super.viewDidLoad() @@ -57,15 +57,23 @@ extension ListViewController { // MARK: - Table View Data Source extension ListViewController: UITableViewDataSource { func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { - return ListHeader() + return ListHeader(viewModel: listViewModel) } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return 2 + return listViewModel.countTodo() } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - return ListCell() + guard let cell = tableView.dequeueReusableCell(withIdentifier: ListCell.identifier) as? ListCell else { + return UITableViewCell() + } + + let todo = listViewModel.getTodo(index: indexPath.row) + + cell.setUpViewModel(ListCellViewModel(todo: todo)) + + return cell } } From ddc2e9b992a1150514f1e68cc88e5792654ee84b Mon Sep 17 00:00:00 2001 From: Moon <1tjen@naver.com> Date: Thu, 28 Sep 2023 18:04:42 +0900 Subject: [PATCH 20/48] =?UTF-8?q?feat:=20TodoDateFormatter,=20DateFormat?= =?UTF-8?q?=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ProjectManager.xcodeproj/project.pbxproj | 4 +++ .../Utility/TodoDateFormatter.swift | 29 +++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 ProjectManager/ProjectManager/Utility/TodoDateFormatter.swift diff --git a/ProjectManager/ProjectManager.xcodeproj/project.pbxproj b/ProjectManager/ProjectManager.xcodeproj/project.pbxproj index 98e8cdf43..94388e86b 100644 --- a/ProjectManager/ProjectManager.xcodeproj/project.pbxproj +++ b/ProjectManager/ProjectManager.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 28913B732AC5666D00E626F7 /* TodoDateFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28913B722AC5666D00E626F7 /* TodoDateFormatter.swift */; }; 28CA66E52AC0F72600FFD7A9 /* ListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28CA66E42AC0F72600FFD7A9 /* ListCell.swift */; }; 28CA66E72AC0FE3100FFD7A9 /* ListHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28CA66E62AC0FE3100FFD7A9 /* ListHeader.swift */; }; 28CA66EB2AC1805300FFD7A9 /* ListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28CA66EA2AC1805300FFD7A9 /* ListViewModel.swift */; }; @@ -21,6 +22,7 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 28913B722AC5666D00E626F7 /* TodoDateFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodoDateFormatter.swift; sourceTree = ""; }; 28CA66E42AC0F72600FFD7A9 /* ListCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListCell.swift; sourceTree = ""; }; 28CA66E62AC0FE3100FFD7A9 /* ListHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListHeader.swift; sourceTree = ""; }; 28CA66EA2AC1805300FFD7A9 /* ListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListViewModel.swift; sourceTree = ""; }; @@ -96,6 +98,7 @@ isa = PBXGroup; children = ( 28CA66F12AC1A08600FFD7A9 /* Reusable.swift */, + 28913B722AC5666D00E626F7 /* TodoDateFormatter.swift */, ); path = Utility; sourceTree = ""; @@ -199,6 +202,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 28913B732AC5666D00E626F7 /* TodoDateFormatter.swift in Sources */, 28CA66ED2AC1808E00FFD7A9 /* Todo.swift in Sources */, 28CA66E52AC0F72600FFD7A9 /* ListCell.swift in Sources */, 28CA66EF2AC1865000FFD7A9 /* ListCellViewModel.swift in Sources */, diff --git a/ProjectManager/ProjectManager/Utility/TodoDateFormatter.swift b/ProjectManager/ProjectManager/Utility/TodoDateFormatter.swift new file mode 100644 index 000000000..0eb909128 --- /dev/null +++ b/ProjectManager/ProjectManager/Utility/TodoDateFormatter.swift @@ -0,0 +1,29 @@ +// +// TodoDateFormatter.swift +// ProjectManager +// +// Created by Moon on 2023/09/28. +// + +import Foundation + +enum DateFormat: String { + case todo = "yyyy. M. d." +} + +final class TodoDateFormatter { + private static let dateFormatter: DateFormatter = { + let formatter = DateFormatter() + formatter.dateFormat = DateFormat.todo.rawValue + + return formatter + }() + + private init() { } + + static func string(from date: Date, format: DateFormat) -> String { + dateFormatter.dateFormat = format.rawValue + + return dateFormatter.string(from: date) + } +} From 0752eea359905763d58f58eb4e06ac2dd8a85782 Mon Sep 17 00:00:00 2001 From: Moon <1tjen@naver.com> Date: Thu, 28 Sep 2023 18:05:12 +0900 Subject: [PATCH 21/48] =?UTF-8?q?refactor:=20Cell=EC=97=90=EC=84=9C=20View?= =?UTF-8?q?Model=EC=9D=84=20=EC=82=AC=EC=9A=A9=ED=95=98=EC=A7=80=20?= =?UTF-8?q?=EC=95=8A=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ProjectManager.xcodeproj/project.pbxproj | 4 -- .../ProjectManager/View/ListCell.swift | 31 ++------ .../View/ListViewController.swift | 6 +- .../ViewModel/ListCellViewModel.swift | 70 ------------------- .../ViewModel/ListViewModel.swift | 16 +---- 5 files changed, 10 insertions(+), 117 deletions(-) delete mode 100644 ProjectManager/ProjectManager/ViewModel/ListCellViewModel.swift diff --git a/ProjectManager/ProjectManager.xcodeproj/project.pbxproj b/ProjectManager/ProjectManager.xcodeproj/project.pbxproj index 94388e86b..5d94c4f71 100644 --- a/ProjectManager/ProjectManager.xcodeproj/project.pbxproj +++ b/ProjectManager/ProjectManager.xcodeproj/project.pbxproj @@ -12,7 +12,6 @@ 28CA66E72AC0FE3100FFD7A9 /* ListHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28CA66E62AC0FE3100FFD7A9 /* ListHeader.swift */; }; 28CA66EB2AC1805300FFD7A9 /* ListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28CA66EA2AC1805300FFD7A9 /* ListViewModel.swift */; }; 28CA66ED2AC1808E00FFD7A9 /* Todo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28CA66EC2AC1808E00FFD7A9 /* Todo.swift */; }; - 28CA66EF2AC1865000FFD7A9 /* ListCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28CA66EE2AC1865000FFD7A9 /* ListCellViewModel.swift */; }; 28CA66F22AC1A08600FFD7A9 /* Reusable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28CA66F12AC1A08600FFD7A9 /* Reusable.swift */; }; C7431F0625F51E1D0094C4CF /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7431F0525F51E1D0094C4CF /* AppDelegate.swift */; }; C7431F0825F51E1D0094C4CF /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7431F0725F51E1D0094C4CF /* SceneDelegate.swift */; }; @@ -27,7 +26,6 @@ 28CA66E62AC0FE3100FFD7A9 /* ListHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListHeader.swift; sourceTree = ""; }; 28CA66EA2AC1805300FFD7A9 /* ListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListViewModel.swift; sourceTree = ""; }; 28CA66EC2AC1808E00FFD7A9 /* Todo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Todo.swift; sourceTree = ""; }; - 28CA66EE2AC1865000FFD7A9 /* ListCellViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListCellViewModel.swift; sourceTree = ""; }; 28CA66F12AC1A08600FFD7A9 /* Reusable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Reusable.swift; sourceTree = ""; }; C7431F0225F51E1D0094C4CF /* ProjectManager.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ProjectManager.app; sourceTree = BUILT_PRODUCTS_DIR; }; C7431F0525F51E1D0094C4CF /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; @@ -53,7 +51,6 @@ isa = PBXGroup; children = ( 28CA66EA2AC1805300FFD7A9 /* ListViewModel.swift */, - 28CA66EE2AC1865000FFD7A9 /* ListCellViewModel.swift */, ); path = ViewModel; sourceTree = ""; @@ -205,7 +202,6 @@ 28913B732AC5666D00E626F7 /* TodoDateFormatter.swift in Sources */, 28CA66ED2AC1808E00FFD7A9 /* Todo.swift in Sources */, 28CA66E52AC0F72600FFD7A9 /* ListCell.swift in Sources */, - 28CA66EF2AC1865000FFD7A9 /* ListCellViewModel.swift in Sources */, 28CA66F22AC1A08600FFD7A9 /* Reusable.swift in Sources */, C7431F0A25F51E1D0094C4CF /* ListViewController.swift in Sources */, 28CA66EB2AC1805300FFD7A9 /* ListViewModel.swift in Sources */, diff --git a/ProjectManager/ProjectManager/View/ListCell.swift b/ProjectManager/ProjectManager/View/ListCell.swift index 91a4b2d04..ac92b8ae5 100644 --- a/ProjectManager/ProjectManager/View/ListCell.swift +++ b/ProjectManager/ProjectManager/View/ListCell.swift @@ -39,13 +39,6 @@ final class ListCell: UITableViewCell { return stackView }() - // ListCell이 재사용 될 때마다 setUpViewModel을 통해 값이 들어오면 바인딩 함 - private var listCellViewModel: ListCellViewModel? { - didSet { - setUpBindings() - } - } - override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) @@ -68,24 +61,12 @@ final class ListCell: UITableViewCell { ) } - // ListCell이 재사용 될 때 ListCellViewModel을 받아옴 - func setUpViewModel(_ viewModel: ListCellViewModel) { - self.listCellViewModel = viewModel - } - - // viewModel을 받을 때마다 바인딩할 것 - private func setUpBindings() { - listCellViewModel?.bindTitle { [weak self] viewModel in - self?.titleLabel.text = viewModel.title - } - - listCellViewModel?.bindDescription { [weak self] viewModel in - self?.descriptionLabel.text = viewModel.description - } - - listCellViewModel?.bindDeadline { [weak self] viewModel in - self?.deadlineLabel.text = viewModel.deadline - } + func setUpContent(_ todo: Todo) { + titleLabel.text = todo.title + descriptionLabel.text = todo.description + deadlineLabel.text = TodoDateFormatter.string( + from: todo.deadline, + format: DateFormat.todo) } } diff --git a/ProjectManager/ProjectManager/View/ListViewController.swift b/ProjectManager/ProjectManager/View/ListViewController.swift index 13b4943a9..d22cb680f 100644 --- a/ProjectManager/ProjectManager/View/ListViewController.swift +++ b/ProjectManager/ProjectManager/View/ListViewController.swift @@ -61,7 +61,7 @@ extension ListViewController: UITableViewDataSource { } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return listViewModel.countTodo() + return listViewModel.todoList.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { @@ -69,9 +69,9 @@ extension ListViewController: UITableViewDataSource { return UITableViewCell() } - let todo = listViewModel.getTodo(index: indexPath.row) + let todo = listViewModel.todoList[indexPath.row] - cell.setUpViewModel(ListCellViewModel(todo: todo)) + cell.setUpContent(todo) return cell } diff --git a/ProjectManager/ProjectManager/ViewModel/ListCellViewModel.swift b/ProjectManager/ProjectManager/ViewModel/ListCellViewModel.swift deleted file mode 100644 index ea6c6c196..000000000 --- a/ProjectManager/ProjectManager/ViewModel/ListCellViewModel.swift +++ /dev/null @@ -1,70 +0,0 @@ -// -// ListCellViewModel.swift -// ProjectManager -// -// Created by Moon on 2023/09/25. -// - -import Foundation - -final class ListCellViewModel { - // 바인딩을 위한 클로저 타입 - typealias bind = ((ListCellViewModel) -> Void) - - // 바인딩할 데이터를 가지고 있는 모델 - private let todo: Todo - - // 바인딩에 사용할 변수들 - var title: String { - didSet { - bindTitle?(self) - } - } - - var description: String { - didSet { - bindDescription?(self) - } - } - - var deadline: String { - didSet { - bindDeadline?(self) - } - } - - // ListCell에서 각각의 레이블을 바인딩하기 위한 클로저들 - private var bindTitle: bind? - private var bindDescription: bind? - private var bindDeadline: bind? - - // deadline 바인딩을 위한 포맷팅용 - private let dateFormatter: DateFormatter = { - let formatter = DateFormatter() - formatter.dateFormat = "yyyy. M. d." - - return formatter - }() - - init(todo: Todo) { - self.todo = todo - title = todo.title - description = todo.description - deadline = dateFormatter.string(from: todo.deadline) - } - - func bindTitle(_ handler: @escaping bind) { - handler(self) - bindTitle = handler - } - - func bindDescription(_ handler: @escaping bind) { - handler(self) - bindDescription = handler - } - - func bindDeadline(_ handler: @escaping bind) { - handler(self) - bindDeadline = handler - } -} diff --git a/ProjectManager/ProjectManager/ViewModel/ListViewModel.swift b/ProjectManager/ProjectManager/ViewModel/ListViewModel.swift index f2fa17265..f363374f9 100644 --- a/ProjectManager/ProjectManager/ViewModel/ListViewModel.swift +++ b/ProjectManager/ProjectManager/ViewModel/ListViewModel.swift @@ -8,12 +8,7 @@ import Foundation final class ListViewModel { - // ListViewController에서 ListCell의 ListCellViewModel로 전달하게될 모델의 배열 - private var todoList: [Todo] { - didSet { - count = String(todoList.count) - } - } + var todoList: [Todo] // ListHeader의 contentAmountLabel와 바인딩할 변수 var count: String { @@ -37,15 +32,6 @@ final class ListViewModel { count = String(todoList.count) } - // ListViewController에서 ListCell의 ListCellViewModel로 전달하기 위해 인덱스로 찾아 리턴 - func getTodo(index: Int) -> Todo { - return todoList[index] - } - - func countTodo() -> Int { - return todoList.count - } - func bindCount(_ handler: @escaping (ListViewModel) -> Void) { handler(self) bindCount = handler From 588d15484560a53ce51db47c7da001b67fe1a07e Mon Sep 17 00:00:00 2001 From: Moon <1tjen@naver.com> Date: Thu, 28 Sep 2023 20:00:17 +0900 Subject: [PATCH 22/48] =?UTF-8?q?refactor:=20todoList=20=EB=AA=A8=EB=8D=B8?= =?UTF-8?q?=20=EB=A1=9C=EB=93=9C=20=EC=8B=9C=EC=A0=90=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ListViewController에서는 viewDidLoad 시점을 쏴주고, ListViewModel에서 viewDidLoad 이벤트를 받아, 데이터 로드를 시도할 수 있도록 수정 --- .../View/ListViewController.swift | 26 ++++++++++++-- .../ViewModel/ListViewModel.swift | 36 +++++++++++++++++-- 2 files changed, 56 insertions(+), 6 deletions(-) diff --git a/ProjectManager/ProjectManager/View/ListViewController.swift b/ProjectManager/ProjectManager/View/ListViewController.swift index d22cb680f..9689047bf 100644 --- a/ProjectManager/ProjectManager/View/ListViewController.swift +++ b/ProjectManager/ProjectManager/View/ListViewController.swift @@ -24,6 +24,24 @@ final class ListViewController: UIViewController { super.viewDidLoad() configureUI() + setUpViewDidLoadNotification() + setUpBindings() + } + + // viewDidLoad 시점을 뷰모델에 알려 데이터를 로드할 수 있도록 함 + private func setUpViewDidLoadNotification() { + NotificationCenter.default + .post( + name: NSNotification.Name("ListViewControllerViewDidLoad"), + object: nil + ) + } + + // listViewModel의 todoList가 바뀌면 테이블뷰를 업데이트 + private func setUpBindings() { + listViewModel.bindTodoList { [weak self] in + self?.tableView.reloadData() + } } } @@ -61,15 +79,17 @@ extension ListViewController: UITableViewDataSource { } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return listViewModel.todoList.count + return listViewModel.todoList?.count ?? .zero } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - guard let cell = tableView.dequeueReusableCell(withIdentifier: ListCell.identifier) as? ListCell else { + guard let cell = tableView.dequeueReusableCell(withIdentifier: ListCell.identifier) as? ListCell, + let todoList = listViewModel.todoList + else { return UITableViewCell() } - let todo = listViewModel.todoList[indexPath.row] + let todo = todoList[indexPath.row] cell.setUpContent(todo) diff --git a/ProjectManager/ProjectManager/ViewModel/ListViewModel.swift b/ProjectManager/ProjectManager/ViewModel/ListViewModel.swift index f363374f9..24e5f3774 100644 --- a/ProjectManager/ProjectManager/ViewModel/ListViewModel.swift +++ b/ProjectManager/ProjectManager/ViewModel/ListViewModel.swift @@ -8,7 +8,12 @@ import Foundation final class ListViewModel { - var todoList: [Todo] + // Model이 변경되면 ListViewController의 테이블뷰를 업데이트 + var todoList: [Todo]? { + didSet { + bindTodoList?() + } + } // ListHeader의 contentAmountLabel와 바인딩할 변수 var count: String { @@ -16,10 +21,32 @@ final class ListViewModel { bindCount?(self) } } + // ListHeader의 contentAmountLabel와 바인딩하기 위한 클로저 private var bindCount: ((ListViewModel) -> Void)? + // ListViewController의 테이블뷰를 업데이트하기 위한 클로저 + private var bindTodoList: (() -> Void)? + init() { + count = String(todoList?.count ?? 0) + + setUpNotifications() + } + + // ListViewController의 viewDidLoad 시점을 받아 모델을 로드할 수 있도록 함 + private func setUpNotifications() { + NotificationCenter.default + .addObserver( + self, + selector: #selector(loadTodoList), + name: NSNotification.Name("ListViewControllerViewDidLoad"), + object: nil + ) + } + + @objc + private func loadTodoList() { todoList = [ Todo(title: "1", description: "111", deadline: Date()), Todo(title: "2", description: "222", deadline: Date()), @@ -28,12 +55,15 @@ final class ListViewModel { Todo(title: "5", description: "555", deadline: Date()), Todo(title: "6", description: "666", deadline: Date()) ] - - count = String(todoList.count) } func bindCount(_ handler: @escaping (ListViewModel) -> Void) { handler(self) bindCount = handler } + + func bindTodoList(_ handler: @escaping () -> Void) { + handler() + bindTodoList = handler + } } From cfbe0fd17bb2e3357b01a6e7b11c1eb334b93cbf Mon Sep 17 00:00:00 2001 From: Moon <1tjen@naver.com> Date: Thu, 28 Sep 2023 20:05:55 +0900 Subject: [PATCH 23/48] =?UTF-8?q?feat:=20Array=20extension=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=EB=B0=8F=20safe=20subscript=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ProjectManager.xcodeproj/project.pbxproj | 12 ++++++++++++ .../ProjectManager/Utility/Extension/Array+.swift | 12 ++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 ProjectManager/ProjectManager/Utility/Extension/Array+.swift diff --git a/ProjectManager/ProjectManager.xcodeproj/project.pbxproj b/ProjectManager/ProjectManager.xcodeproj/project.pbxproj index 5d94c4f71..77de4771c 100644 --- a/ProjectManager/ProjectManager.xcodeproj/project.pbxproj +++ b/ProjectManager/ProjectManager.xcodeproj/project.pbxproj @@ -8,6 +8,7 @@ /* Begin PBXBuildFile section */ 28913B732AC5666D00E626F7 /* TodoDateFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28913B722AC5666D00E626F7 /* TodoDateFormatter.swift */; }; + 28913B762AC5950E00E626F7 /* Array+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28913B752AC5950E00E626F7 /* Array+.swift */; }; 28CA66E52AC0F72600FFD7A9 /* ListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28CA66E42AC0F72600FFD7A9 /* ListCell.swift */; }; 28CA66E72AC0FE3100FFD7A9 /* ListHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28CA66E62AC0FE3100FFD7A9 /* ListHeader.swift */; }; 28CA66EB2AC1805300FFD7A9 /* ListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28CA66EA2AC1805300FFD7A9 /* ListViewModel.swift */; }; @@ -22,6 +23,7 @@ /* Begin PBXFileReference section */ 28913B722AC5666D00E626F7 /* TodoDateFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodoDateFormatter.swift; sourceTree = ""; }; + 28913B752AC5950E00E626F7 /* Array+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array+.swift"; sourceTree = ""; }; 28CA66E42AC0F72600FFD7A9 /* ListCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListCell.swift; sourceTree = ""; }; 28CA66E62AC0FE3100FFD7A9 /* ListHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListHeader.swift; sourceTree = ""; }; 28CA66EA2AC1805300FFD7A9 /* ListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListViewModel.swift; sourceTree = ""; }; @@ -47,6 +49,14 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 28913B742AC594EE00E626F7 /* Extension */ = { + isa = PBXGroup; + children = ( + 28913B752AC5950E00E626F7 /* Array+.swift */, + ); + path = Extension; + sourceTree = ""; + }; 2897D3752AC05F8400662BAA /* ViewModel */ = { isa = PBXGroup; children = ( @@ -94,6 +104,7 @@ 28CA66F02AC1A02C00FFD7A9 /* Utility */ = { isa = PBXGroup; children = ( + 28913B742AC594EE00E626F7 /* Extension */, 28CA66F12AC1A08600FFD7A9 /* Reusable.swift */, 28913B722AC5666D00E626F7 /* TodoDateFormatter.swift */, ); @@ -208,6 +219,7 @@ C7431F0625F51E1D0094C4CF /* AppDelegate.swift in Sources */, 28CA66E72AC0FE3100FFD7A9 /* ListHeader.swift in Sources */, C7431F0825F51E1D0094C4CF /* SceneDelegate.swift in Sources */, + 28913B762AC5950E00E626F7 /* Array+.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/ProjectManager/ProjectManager/Utility/Extension/Array+.swift b/ProjectManager/ProjectManager/Utility/Extension/Array+.swift new file mode 100644 index 000000000..d61a173a6 --- /dev/null +++ b/ProjectManager/ProjectManager/Utility/Extension/Array+.swift @@ -0,0 +1,12 @@ +// +// Array+.swift +// ProjectManager +// +// Created by Moon on 2023/09/28. +// + +extension Array { + subscript(safe index: Int) -> Element? { + return indices ~= index ? self[index] : nil + } +} From 5693a3a7a5b31a2237fce35764aa72cef0577d00 Mon Sep 17 00:00:00 2001 From: Moon <1tjen@naver.com> Date: Thu, 28 Sep 2023 20:06:30 +0900 Subject: [PATCH 24/48] =?UTF-8?q?refactor:=20=EB=B0=B0=EC=97=B4=20?= =?UTF-8?q?=EC=A0=91=EA=B7=BC=EC=9D=84=20safe=20subscript=EB=A1=9C=20?= =?UTF-8?q?=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ProjectManager/ProjectManager/View/ListViewController.swift | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/ProjectManager/ProjectManager/View/ListViewController.swift b/ProjectManager/ProjectManager/View/ListViewController.swift index 9689047bf..d17f46e7d 100644 --- a/ProjectManager/ProjectManager/View/ListViewController.swift +++ b/ProjectManager/ProjectManager/View/ListViewController.swift @@ -84,13 +84,12 @@ extension ListViewController: UITableViewDataSource { func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { guard let cell = tableView.dequeueReusableCell(withIdentifier: ListCell.identifier) as? ListCell, - let todoList = listViewModel.todoList + let todoList = listViewModel.todoList, + let todo = todoList[safe: indexPath.row] else { return UITableViewCell() } - let todo = todoList[indexPath.row] - cell.setUpContent(todo) return cell From 65dd71b25ca67ebf66dad8baed10fd7968a403ec Mon Sep 17 00:00:00 2001 From: Moon <1tjen@naver.com> Date: Sat, 30 Sep 2023 16:56:28 +0900 Subject: [PATCH 25/48] =?UTF-8?q?fix:=20ListHeader=EC=9D=98=20=EB=B0=B0?= =?UTF-8?q?=EA=B2=BD=EC=83=89=EC=9D=84=20=ED=9A=8C=EC=83=89=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 배경색이 투명이기 때문에 스크롤 시 테이블 뷰의 내용과 겹쳐 보이는 문제 해결 --- ProjectManager/ProjectManager/View/ListHeader.swift | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ProjectManager/ProjectManager/View/ListHeader.swift b/ProjectManager/ProjectManager/View/ListHeader.swift index 27d4f9424..1abc7a331 100644 --- a/ProjectManager/ProjectManager/View/ListHeader.swift +++ b/ProjectManager/ProjectManager/View/ListHeader.swift @@ -62,10 +62,15 @@ final class ListHeader: UIView { // MARK: - Configure UI extension ListHeader { private func configureUI() { + setUpView() addSubviews() setUpConstraints() } + private func setUpView() { + backgroundColor = .systemGray6 + } + private func addSubviews() { [titleLabel, contentAmountLabel].forEach { contentStackView.addArrangedSubview($0) From 0ffc468d93b2ce91a1ce0d14912de101ef507204 Mon Sep 17 00:00:00 2001 From: Moon <1tjen@naver.com> Date: Sat, 30 Sep 2023 16:58:01 +0900 Subject: [PATCH 26/48] =?UTF-8?q?feat:=20NavigationController=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Application/SceneDelegate.swift | 2 +- .../View/ListViewController.swift | 21 +++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/ProjectManager/ProjectManager/Application/SceneDelegate.swift b/ProjectManager/ProjectManager/Application/SceneDelegate.swift index f19eb9fab..59112a618 100644 --- a/ProjectManager/ProjectManager/Application/SceneDelegate.swift +++ b/ProjectManager/ProjectManager/Application/SceneDelegate.swift @@ -15,7 +15,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { guard let windowScene = (scene as? UIWindowScene) else { return } window = UIWindow(windowScene: windowScene) - window?.rootViewController = ListViewController() + window?.rootViewController = UINavigationController(rootViewController: ListViewController()) window?.makeKeyAndVisible() } diff --git a/ProjectManager/ProjectManager/View/ListViewController.swift b/ProjectManager/ProjectManager/View/ListViewController.swift index d17f46e7d..87875bd6b 100644 --- a/ProjectManager/ProjectManager/View/ListViewController.swift +++ b/ProjectManager/ProjectManager/View/ListViewController.swift @@ -43,15 +43,36 @@ final class ListViewController: UIViewController { self?.tableView.reloadData() } } + + @objc + private func addTodo() { + + } } // MARK: - Configure UI extension ListViewController { private func configureUI() { + setUpView() + setUpNavigation() addSubviews() setUpTableViewConstraints() } + private func setUpView() { + view.backgroundColor = .systemBackground + } + + private func setUpNavigation() { + navigationItem.title = "Project Manager" + navigationItem.rightBarButtonItem = UIBarButtonItem( + image: UIImage(systemName: "plus"), + style: .plain, + target: self, + action: #selector(addTodo) + ) + } + private func addSubviews() { view.addSubview(tableView) } From 430242200356eac98652a815bec089dd57854449 Mon Sep 17 00:00:00 2001 From: Moon <1tjen@naver.com> Date: Sat, 30 Sep 2023 17:20:51 +0900 Subject: [PATCH 27/48] =?UTF-8?q?feat:=20ToDo=20CoreData=20=EB=AA=A8?= =?UTF-8?q?=EB=8D=B8=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ProjectManager.xcodeproj/project.pbxproj | 33 +++++++++++++++++++ .../Model/ToDo/ToDo+CoreDataClass.swift | 15 +++++++++ .../Model/ToDo/ToDo+CoreDataProperties.swift | 28 ++++++++++++++++ .../ToDoModel.xcdatamodel/contents | 9 +++++ 4 files changed, 85 insertions(+) create mode 100644 ProjectManager/ProjectManager/Model/ToDo/ToDo+CoreDataClass.swift create mode 100644 ProjectManager/ProjectManager/Model/ToDo/ToDo+CoreDataProperties.swift create mode 100644 ProjectManager/ProjectManager/Model/ToDo/ToDo.xcdatamodeld/ToDoModel.xcdatamodel/contents diff --git a/ProjectManager/ProjectManager.xcodeproj/project.pbxproj b/ProjectManager/ProjectManager.xcodeproj/project.pbxproj index 77de4771c..40947213b 100644 --- a/ProjectManager/ProjectManager.xcodeproj/project.pbxproj +++ b/ProjectManager/ProjectManager.xcodeproj/project.pbxproj @@ -9,6 +9,9 @@ /* Begin PBXBuildFile section */ 28913B732AC5666D00E626F7 /* TodoDateFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28913B722AC5666D00E626F7 /* TodoDateFormatter.swift */; }; 28913B762AC5950E00E626F7 /* Array+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28913B752AC5950E00E626F7 /* Array+.swift */; }; + 28913B7A2AC5AC9A00E626F7 /* ToDo.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 28913B782AC5AC9A00E626F7 /* ToDo.xcdatamodeld */; }; + 28913B7F2AC8033000E626F7 /* ToDo+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28913B7D2AC8033000E626F7 /* ToDo+CoreDataClass.swift */; }; + 28913B802AC8033000E626F7 /* ToDo+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28913B7E2AC8033000E626F7 /* ToDo+CoreDataProperties.swift */; }; 28CA66E52AC0F72600FFD7A9 /* ListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28CA66E42AC0F72600FFD7A9 /* ListCell.swift */; }; 28CA66E72AC0FE3100FFD7A9 /* ListHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28CA66E62AC0FE3100FFD7A9 /* ListHeader.swift */; }; 28CA66EB2AC1805300FFD7A9 /* ListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28CA66EA2AC1805300FFD7A9 /* ListViewModel.swift */; }; @@ -24,6 +27,9 @@ /* Begin PBXFileReference section */ 28913B722AC5666D00E626F7 /* TodoDateFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodoDateFormatter.swift; sourceTree = ""; }; 28913B752AC5950E00E626F7 /* Array+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array+.swift"; sourceTree = ""; }; + 28913B792AC5AC9A00E626F7 /* ToDoModel.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = ToDoModel.xcdatamodel; sourceTree = ""; }; + 28913B7D2AC8033000E626F7 /* ToDo+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "ToDo+CoreDataClass.swift"; path = "ProjectManager/Model/ToDo/ToDo+CoreDataClass.swift"; sourceTree = ""; }; + 28913B7E2AC8033000E626F7 /* ToDo+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "ToDo+CoreDataProperties.swift"; path = "ProjectManager/Model/ToDo/ToDo+CoreDataProperties.swift"; sourceTree = ""; }; 28CA66E42AC0F72600FFD7A9 /* ListCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListCell.swift; sourceTree = ""; }; 28CA66E62AC0FE3100FFD7A9 /* ListHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListHeader.swift; sourceTree = ""; }; 28CA66EA2AC1805300FFD7A9 /* ListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListViewModel.swift; sourceTree = ""; }; @@ -57,6 +63,14 @@ path = Extension; sourceTree = ""; }; + 28913B772AC5AC3100E626F7 /* ToDo */ = { + isa = PBXGroup; + children = ( + 28913B782AC5AC9A00E626F7 /* ToDo.xcdatamodeld */, + ); + path = ToDo; + sourceTree = ""; + }; 2897D3752AC05F8400662BAA /* ViewModel */ = { isa = PBXGroup; children = ( @@ -79,6 +93,7 @@ 2897D3772AC05F9500662BAA /* Model */ = { isa = PBXGroup; children = ( + 28913B772AC5AC3100E626F7 /* ToDo */, 28CA66EC2AC1808E00FFD7A9 /* Todo.swift */, ); path = Model; @@ -114,6 +129,8 @@ C7431EF925F51E1D0094C4CF = { isa = PBXGroup; children = ( + 28913B7D2AC8033000E626F7 /* ToDo+CoreDataClass.swift */, + 28913B7E2AC8033000E626F7 /* ToDo+CoreDataProperties.swift */, C7431F0425F51E1D0094C4CF /* ProjectManager */, C7431F0325F51E1D0094C4CF /* Products */, ); @@ -213,12 +230,15 @@ 28913B732AC5666D00E626F7 /* TodoDateFormatter.swift in Sources */, 28CA66ED2AC1808E00FFD7A9 /* Todo.swift in Sources */, 28CA66E52AC0F72600FFD7A9 /* ListCell.swift in Sources */, + 28913B802AC8033000E626F7 /* ToDo+CoreDataProperties.swift in Sources */, 28CA66F22AC1A08600FFD7A9 /* Reusable.swift in Sources */, C7431F0A25F51E1D0094C4CF /* ListViewController.swift in Sources */, 28CA66EB2AC1805300FFD7A9 /* ListViewModel.swift in Sources */, C7431F0625F51E1D0094C4CF /* AppDelegate.swift in Sources */, 28CA66E72AC0FE3100FFD7A9 /* ListHeader.swift in Sources */, + 28913B7F2AC8033000E626F7 /* ToDo+CoreDataClass.swift in Sources */, C7431F0825F51E1D0094C4CF /* SceneDelegate.swift in Sources */, + 28913B7A2AC5AC9A00E626F7 /* ToDo.xcdatamodeld in Sources */, 28913B762AC5950E00E626F7 /* Array+.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -413,6 +433,19 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCVersionGroup section */ + 28913B782AC5AC9A00E626F7 /* ToDo.xcdatamodeld */ = { + isa = XCVersionGroup; + children = ( + 28913B792AC5AC9A00E626F7 /* ToDoModel.xcdatamodel */, + ); + currentVersion = 28913B792AC5AC9A00E626F7 /* ToDoModel.xcdatamodel */; + path = ToDo.xcdatamodeld; + sourceTree = ""; + versionGroupType = wrapper.xcdatamodel; + }; +/* End XCVersionGroup section */ }; rootObject = C7431EFA25F51E1D0094C4CF /* Project object */; } diff --git a/ProjectManager/ProjectManager/Model/ToDo/ToDo+CoreDataClass.swift b/ProjectManager/ProjectManager/Model/ToDo/ToDo+CoreDataClass.swift new file mode 100644 index 000000000..2257fb1ff --- /dev/null +++ b/ProjectManager/ProjectManager/Model/ToDo/ToDo+CoreDataClass.swift @@ -0,0 +1,15 @@ +// +// ToDo+CoreDataClass.swift +// ProjectManager +// +// Created by 조호준 on 2023/09/30. +// +// + +import Foundation +import CoreData + +@objc(ToDo) +public class ToDo: NSManagedObject { + +} diff --git a/ProjectManager/ProjectManager/Model/ToDo/ToDo+CoreDataProperties.swift b/ProjectManager/ProjectManager/Model/ToDo/ToDo+CoreDataProperties.swift new file mode 100644 index 000000000..e2c16692f --- /dev/null +++ b/ProjectManager/ProjectManager/Model/ToDo/ToDo+CoreDataProperties.swift @@ -0,0 +1,28 @@ +// +// ToDo+CoreDataProperties.swift +// ProjectManager +// +// Created by 조호준 on 2023/09/30. +// +// + +import Foundation +import CoreData + + +extension ToDo { + + @nonobjc public class func fetchRequest() -> NSFetchRequest { + return NSFetchRequest(entityName: "ToDo") + } + + @NSManaged public var title: String? + @NSManaged public var body: String? + @NSManaged public var deadline: Date? + @NSManaged public var category: String? + +} + +extension ToDo : Identifiable { + +} diff --git a/ProjectManager/ProjectManager/Model/ToDo/ToDo.xcdatamodeld/ToDoModel.xcdatamodel/contents b/ProjectManager/ProjectManager/Model/ToDo/ToDo.xcdatamodeld/ToDoModel.xcdatamodel/contents new file mode 100644 index 000000000..2d7aa960f --- /dev/null +++ b/ProjectManager/ProjectManager/Model/ToDo/ToDo.xcdatamodeld/ToDoModel.xcdatamodel/contents @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file From a78074637886586536642784f8183d5bdb855ccd Mon Sep 17 00:00:00 2001 From: Moon <1tjen@naver.com> Date: Sat, 30 Sep 2023 17:23:27 +0900 Subject: [PATCH 28/48] =?UTF-8?q?feat:=20Category=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ProjectManager.xcodeproj/project.pbxproj | 4 ++++ .../ProjectManager/Model/ToDo/Category.swift | 12 ++++++++++++ 2 files changed, 16 insertions(+) create mode 100644 ProjectManager/ProjectManager/Model/ToDo/Category.swift diff --git a/ProjectManager/ProjectManager.xcodeproj/project.pbxproj b/ProjectManager/ProjectManager.xcodeproj/project.pbxproj index 40947213b..292fecc8e 100644 --- a/ProjectManager/ProjectManager.xcodeproj/project.pbxproj +++ b/ProjectManager/ProjectManager.xcodeproj/project.pbxproj @@ -10,6 +10,7 @@ 28913B732AC5666D00E626F7 /* TodoDateFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28913B722AC5666D00E626F7 /* TodoDateFormatter.swift */; }; 28913B762AC5950E00E626F7 /* Array+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28913B752AC5950E00E626F7 /* Array+.swift */; }; 28913B7A2AC5AC9A00E626F7 /* ToDo.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 28913B782AC5AC9A00E626F7 /* ToDo.xcdatamodeld */; }; + 28913B7C2AC5AD1300E626F7 /* Category.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28913B7B2AC5AD1300E626F7 /* Category.swift */; }; 28913B7F2AC8033000E626F7 /* ToDo+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28913B7D2AC8033000E626F7 /* ToDo+CoreDataClass.swift */; }; 28913B802AC8033000E626F7 /* ToDo+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28913B7E2AC8033000E626F7 /* ToDo+CoreDataProperties.swift */; }; 28CA66E52AC0F72600FFD7A9 /* ListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28CA66E42AC0F72600FFD7A9 /* ListCell.swift */; }; @@ -28,6 +29,7 @@ 28913B722AC5666D00E626F7 /* TodoDateFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodoDateFormatter.swift; sourceTree = ""; }; 28913B752AC5950E00E626F7 /* Array+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array+.swift"; sourceTree = ""; }; 28913B792AC5AC9A00E626F7 /* ToDoModel.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = ToDoModel.xcdatamodel; sourceTree = ""; }; + 28913B7B2AC5AD1300E626F7 /* Category.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Category.swift; sourceTree = ""; }; 28913B7D2AC8033000E626F7 /* ToDo+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "ToDo+CoreDataClass.swift"; path = "ProjectManager/Model/ToDo/ToDo+CoreDataClass.swift"; sourceTree = ""; }; 28913B7E2AC8033000E626F7 /* ToDo+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "ToDo+CoreDataProperties.swift"; path = "ProjectManager/Model/ToDo/ToDo+CoreDataProperties.swift"; sourceTree = ""; }; 28CA66E42AC0F72600FFD7A9 /* ListCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListCell.swift; sourceTree = ""; }; @@ -67,6 +69,7 @@ isa = PBXGroup; children = ( 28913B782AC5AC9A00E626F7 /* ToDo.xcdatamodeld */, + 28913B7B2AC5AD1300E626F7 /* Category.swift */, ); path = ToDo; sourceTree = ""; @@ -240,6 +243,7 @@ C7431F0825F51E1D0094C4CF /* SceneDelegate.swift in Sources */, 28913B7A2AC5AC9A00E626F7 /* ToDo.xcdatamodeld in Sources */, 28913B762AC5950E00E626F7 /* Array+.swift in Sources */, + 28913B7C2AC5AD1300E626F7 /* Category.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/ProjectManager/ProjectManager/Model/ToDo/Category.swift b/ProjectManager/ProjectManager/Model/ToDo/Category.swift new file mode 100644 index 000000000..ff9c2ed1a --- /dev/null +++ b/ProjectManager/ProjectManager/Model/ToDo/Category.swift @@ -0,0 +1,12 @@ +// +// Category.swift +// ProjectManager +// +// Created by Moon on 2023/09/28. +// + +enum Category: String { + case todo = "todo" + case doing = "doing" + case done = "done" +} From d8b8f57d59ce28ef49f70195f9213a2629930a7a Mon Sep 17 00:00:00 2001 From: Moon <1tjen@naver.com> Date: Sun, 1 Oct 2023 16:00:27 +0900 Subject: [PATCH 29/48] =?UTF-8?q?refactor:=20Category=EC=9D=98=20rawValue?= =?UTF-8?q?=EB=A5=BC=20=EB=8C=80=EB=AC=B8=EC=9E=90=EB=A1=9C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ProjectManager/ProjectManager/Model/ToDo/Category.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ProjectManager/ProjectManager/Model/ToDo/Category.swift b/ProjectManager/ProjectManager/Model/ToDo/Category.swift index ff9c2ed1a..4f94dcd2d 100644 --- a/ProjectManager/ProjectManager/Model/ToDo/Category.swift +++ b/ProjectManager/ProjectManager/Model/ToDo/Category.swift @@ -6,7 +6,7 @@ // enum Category: String { - case todo = "todo" - case doing = "doing" - case done = "done" + case todo = "TODO" + case doing = "DOING" + case done = "DONE" } From cfdc5e2d7831ae6bac0df788f467ea4d3b1931fd Mon Sep 17 00:00:00 2001 From: Moon <1tjen@naver.com> Date: Sun, 1 Oct 2023 16:02:29 +0900 Subject: [PATCH 30/48] =?UTF-8?q?feat:=20UIFont=20extension=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20TextStyle=EA=B3=BC=20Weight=EB=A5=BC=20=EB=8F=99?= =?UTF-8?q?=EC=8B=9C=EC=97=90=20=EC=A0=81=EC=9A=A9=ED=95=A0=20=EC=88=98=20?= =?UTF-8?q?=EC=9E=88=EB=8F=84=EB=A1=9D=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ProjectManager.xcodeproj/project.pbxproj | 4 ++++ .../Utility/Extension/UIFont+.swift | 18 ++++++++++++++++++ .../ProjectManager/View/ListCell.swift | 5 ++++- .../ProjectManager/View/ListHeader.swift | 4 +++- .../View/ListViewController.swift | 4 ++++ 5 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 ProjectManager/ProjectManager/Utility/Extension/UIFont+.swift diff --git a/ProjectManager/ProjectManager.xcodeproj/project.pbxproj b/ProjectManager/ProjectManager.xcodeproj/project.pbxproj index 292fecc8e..a7b04bc9a 100644 --- a/ProjectManager/ProjectManager.xcodeproj/project.pbxproj +++ b/ProjectManager/ProjectManager.xcodeproj/project.pbxproj @@ -18,6 +18,7 @@ 28CA66EB2AC1805300FFD7A9 /* ListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28CA66EA2AC1805300FFD7A9 /* ListViewModel.swift */; }; 28CA66ED2AC1808E00FFD7A9 /* Todo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28CA66EC2AC1808E00FFD7A9 /* Todo.swift */; }; 28CA66F22AC1A08600FFD7A9 /* Reusable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28CA66F12AC1A08600FFD7A9 /* Reusable.swift */; }; + 28DBCB982AC94A5F00DFC58E /* UIFont+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28DBCB972AC94A5F00DFC58E /* UIFont+.swift */; }; C7431F0625F51E1D0094C4CF /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7431F0525F51E1D0094C4CF /* AppDelegate.swift */; }; C7431F0825F51E1D0094C4CF /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7431F0725F51E1D0094C4CF /* SceneDelegate.swift */; }; C7431F0A25F51E1D0094C4CF /* ListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7431F0925F51E1D0094C4CF /* ListViewController.swift */; }; @@ -37,6 +38,7 @@ 28CA66EA2AC1805300FFD7A9 /* ListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListViewModel.swift; sourceTree = ""; }; 28CA66EC2AC1808E00FFD7A9 /* Todo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Todo.swift; sourceTree = ""; }; 28CA66F12AC1A08600FFD7A9 /* Reusable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Reusable.swift; sourceTree = ""; }; + 28DBCB972AC94A5F00DFC58E /* UIFont+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIFont+.swift"; sourceTree = ""; }; C7431F0225F51E1D0094C4CF /* ProjectManager.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ProjectManager.app; sourceTree = BUILT_PRODUCTS_DIR; }; C7431F0525F51E1D0094C4CF /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; C7431F0725F51E1D0094C4CF /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; @@ -61,6 +63,7 @@ isa = PBXGroup; children = ( 28913B752AC5950E00E626F7 /* Array+.swift */, + 28DBCB972AC94A5F00DFC58E /* UIFont+.swift */, ); path = Extension; sourceTree = ""; @@ -237,6 +240,7 @@ 28CA66F22AC1A08600FFD7A9 /* Reusable.swift in Sources */, C7431F0A25F51E1D0094C4CF /* ListViewController.swift in Sources */, 28CA66EB2AC1805300FFD7A9 /* ListViewModel.swift in Sources */, + 28DBCB982AC94A5F00DFC58E /* UIFont+.swift in Sources */, C7431F0625F51E1D0094C4CF /* AppDelegate.swift in Sources */, 28CA66E72AC0FE3100FFD7A9 /* ListHeader.swift in Sources */, 28913B7F2AC8033000E626F7 /* ToDo+CoreDataClass.swift in Sources */, diff --git a/ProjectManager/ProjectManager/Utility/Extension/UIFont+.swift b/ProjectManager/ProjectManager/Utility/Extension/UIFont+.swift new file mode 100644 index 000000000..38a9bf168 --- /dev/null +++ b/ProjectManager/ProjectManager/Utility/Extension/UIFont+.swift @@ -0,0 +1,18 @@ +// +// UIFont+.swift +// ProjectManager +// +// Created by Moon on 2023/10/01. +// + +import UIKit + +extension UIFont { + static func preferredFont(for style: TextStyle, weight: Weight) -> UIFont { + let descriptor = UIFontDescriptor.preferredFontDescriptor(withTextStyle: style) + let font = UIFont.systemFont(ofSize: descriptor.pointSize, weight: weight) + let metrics = UIFontMetrics(forTextStyle: style) + + return metrics.scaledFont(for: font) + } +} diff --git a/ProjectManager/ProjectManager/View/ListCell.swift b/ProjectManager/ProjectManager/View/ListCell.swift index ac92b8ae5..4efbbaf29 100644 --- a/ProjectManager/ProjectManager/View/ListCell.swift +++ b/ProjectManager/ProjectManager/View/ListCell.swift @@ -10,7 +10,8 @@ import UIKit final class ListCell: UITableViewCell { private let titleLabel: UILabel = { let label = UILabel() - label.font = UIFont.preferredFont(forTextStyle: .title3) + label.font = UIFont.preferredFont(for: .title3, weight: .semibold) + label.adjustsFontForContentSizeCategory = true return label }() @@ -20,6 +21,7 @@ final class ListCell: UITableViewCell { label.font = UIFont.preferredFont(forTextStyle: .body) label.textColor = .systemGray3 label.numberOfLines = 3 + label.adjustsFontForContentSizeCategory = true return label }() @@ -27,6 +29,7 @@ final class ListCell: UITableViewCell { private let deadlineLabel: UILabel = { let label = UILabel() label.font = UIFont.preferredFont(forTextStyle: .callout) + label.adjustsFontForContentSizeCategory = true return label }() diff --git a/ProjectManager/ProjectManager/View/ListHeader.swift b/ProjectManager/ProjectManager/View/ListHeader.swift index 1abc7a331..78c7e12ef 100644 --- a/ProjectManager/ProjectManager/View/ListHeader.swift +++ b/ProjectManager/ProjectManager/View/ListHeader.swift @@ -10,8 +10,9 @@ import UIKit final class ListHeader: UIView { private let titleLabel: UILabel = { let label = UILabel() - label.font = UIFont.preferredFont(forTextStyle: .largeTitle) + label.font = UIFont.preferredFont(for: .largeTitle, weight: .semibold) label.text = "TODO" + label.adjustsFontForContentSizeCategory = true return label }() @@ -21,6 +22,7 @@ final class ListHeader: UIView { label.font = UIFont.preferredFont(forTextStyle: .title2) label.textColor = .white label.textAlignment = .center + label.adjustsFontForContentSizeCategory = true label.layer.backgroundColor = UIColor.black.cgColor label.layer.cornerRadius = 20 diff --git a/ProjectManager/ProjectManager/View/ListViewController.swift b/ProjectManager/ProjectManager/View/ListViewController.swift index 87875bd6b..60827368d 100644 --- a/ProjectManager/ProjectManager/View/ListViewController.swift +++ b/ProjectManager/ProjectManager/View/ListViewController.swift @@ -65,6 +65,10 @@ extension ListViewController { private func setUpNavigation() { navigationItem.title = "Project Manager" + navigationController?.navigationBar + .titleTextAttributes = [ + NSAttributedString.Key.font : UIFont.preferredFont(for: .title3, weight: .bold) + ] navigationItem.rightBarButtonItem = UIBarButtonItem( image: UIImage(systemName: "plus"), style: .plain, From eb5d9fa9fc0ac54390f09bd909f613084d3110a6 Mon Sep 17 00:00:00 2001 From: Moon <1tjen@naver.com> Date: Sun, 1 Oct 2023 16:06:05 +0900 Subject: [PATCH 31/48] =?UTF-8?q?feat:=20UITextField=EC=97=90=20inset?= =?UTF-8?q?=EC=9D=84=20=EC=A0=81=EC=9A=A9=ED=95=98=EA=B8=B0=20=EC=9C=84?= =?UTF-8?q?=ED=95=9C=20InsetTextField=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ProjectManager.xcodeproj/project.pbxproj | 4 +++ .../ProjectManager/View/InsetTextField.swift | 31 +++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 ProjectManager/ProjectManager/View/InsetTextField.swift diff --git a/ProjectManager/ProjectManager.xcodeproj/project.pbxproj b/ProjectManager/ProjectManager.xcodeproj/project.pbxproj index a7b04bc9a..f428f294f 100644 --- a/ProjectManager/ProjectManager.xcodeproj/project.pbxproj +++ b/ProjectManager/ProjectManager.xcodeproj/project.pbxproj @@ -18,6 +18,7 @@ 28CA66EB2AC1805300FFD7A9 /* ListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28CA66EA2AC1805300FFD7A9 /* ListViewModel.swift */; }; 28CA66ED2AC1808E00FFD7A9 /* Todo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28CA66EC2AC1808E00FFD7A9 /* Todo.swift */; }; 28CA66F22AC1A08600FFD7A9 /* Reusable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28CA66F12AC1A08600FFD7A9 /* Reusable.swift */; }; + 28DBCB962AC9292C00DFC58E /* InsetTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28DBCB952AC9292C00DFC58E /* InsetTextField.swift */; }; 28DBCB982AC94A5F00DFC58E /* UIFont+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28DBCB972AC94A5F00DFC58E /* UIFont+.swift */; }; C7431F0625F51E1D0094C4CF /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7431F0525F51E1D0094C4CF /* AppDelegate.swift */; }; C7431F0825F51E1D0094C4CF /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7431F0725F51E1D0094C4CF /* SceneDelegate.swift */; }; @@ -38,6 +39,7 @@ 28CA66EA2AC1805300FFD7A9 /* ListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListViewModel.swift; sourceTree = ""; }; 28CA66EC2AC1808E00FFD7A9 /* Todo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Todo.swift; sourceTree = ""; }; 28CA66F12AC1A08600FFD7A9 /* Reusable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Reusable.swift; sourceTree = ""; }; + 28DBCB952AC9292C00DFC58E /* InsetTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InsetTextField.swift; sourceTree = ""; }; 28DBCB972AC94A5F00DFC58E /* UIFont+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIFont+.swift"; sourceTree = ""; }; C7431F0225F51E1D0094C4CF /* ProjectManager.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ProjectManager.app; sourceTree = BUILT_PRODUCTS_DIR; }; C7431F0525F51E1D0094C4CF /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; @@ -92,6 +94,7 @@ C7431F0925F51E1D0094C4CF /* ListViewController.swift */, 28CA66E62AC0FE3100FFD7A9 /* ListHeader.swift */, 28CA66E42AC0F72600FFD7A9 /* ListCell.swift */, + 28DBCB952AC9292C00DFC58E /* InsetTextField.swift */, ); path = View; sourceTree = ""; @@ -235,6 +238,7 @@ files = ( 28913B732AC5666D00E626F7 /* TodoDateFormatter.swift in Sources */, 28CA66ED2AC1808E00FFD7A9 /* Todo.swift in Sources */, + 28DBCB962AC9292C00DFC58E /* InsetTextField.swift in Sources */, 28CA66E52AC0F72600FFD7A9 /* ListCell.swift in Sources */, 28913B802AC8033000E626F7 /* ToDo+CoreDataProperties.swift in Sources */, 28CA66F22AC1A08600FFD7A9 /* Reusable.swift in Sources */, diff --git a/ProjectManager/ProjectManager/View/InsetTextField.swift b/ProjectManager/ProjectManager/View/InsetTextField.swift new file mode 100644 index 000000000..4956df389 --- /dev/null +++ b/ProjectManager/ProjectManager/View/InsetTextField.swift @@ -0,0 +1,31 @@ +// +// InsetTextField.swift +// ProjectManager +// +// Created by Moon on 2023/10/01. +// + +import UIKit + +final class InsetTextField: UITextField { + private let inset = UIEdgeInsets(top: 10, left: 15, bottom: 10, right: 15) + + override func textRect(forBounds bounds: CGRect) -> CGRect { + return bounds.inset(by: inset) + } + + override func placeholderRect(forBounds bounds: CGRect) -> CGRect { + return bounds.inset(by: inset) + } + + override func editingRect(forBounds bounds: CGRect) -> CGRect { + let editingInsets = UIEdgeInsets( + top: inset.top, + left: inset.left, + bottom: inset.bottom, + right: inset.right + ) + + return bounds.inset(by: editingInsets) + } +} From 9078bedffa71177cae7266b93e291f476c6f97e6 Mon Sep 17 00:00:00 2001 From: Moon <1tjen@naver.com> Date: Sun, 1 Oct 2023 16:06:50 +0900 Subject: [PATCH 32/48] =?UTF-8?q?feat:=20ToDoDetailViewController=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ProjectManager.xcodeproj/project.pbxproj | 4 + .../View/ListViewController.swift | 2 + .../View/ToDoDetailViewController.swift | 251 ++++++++++++++++++ 3 files changed, 257 insertions(+) create mode 100644 ProjectManager/ProjectManager/View/ToDoDetailViewController.swift diff --git a/ProjectManager/ProjectManager.xcodeproj/project.pbxproj b/ProjectManager/ProjectManager.xcodeproj/project.pbxproj index f428f294f..87ffab521 100644 --- a/ProjectManager/ProjectManager.xcodeproj/project.pbxproj +++ b/ProjectManager/ProjectManager.xcodeproj/project.pbxproj @@ -13,6 +13,7 @@ 28913B7C2AC5AD1300E626F7 /* Category.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28913B7B2AC5AD1300E626F7 /* Category.swift */; }; 28913B7F2AC8033000E626F7 /* ToDo+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28913B7D2AC8033000E626F7 /* ToDo+CoreDataClass.swift */; }; 28913B802AC8033000E626F7 /* ToDo+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28913B7E2AC8033000E626F7 /* ToDo+CoreDataProperties.swift */; }; + 28913B852AC83ACE00E626F7 /* ToDoDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28913B842AC83ACE00E626F7 /* ToDoDetailViewController.swift */; }; 28CA66E52AC0F72600FFD7A9 /* ListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28CA66E42AC0F72600FFD7A9 /* ListCell.swift */; }; 28CA66E72AC0FE3100FFD7A9 /* ListHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28CA66E62AC0FE3100FFD7A9 /* ListHeader.swift */; }; 28CA66EB2AC1805300FFD7A9 /* ListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28CA66EA2AC1805300FFD7A9 /* ListViewModel.swift */; }; @@ -34,6 +35,7 @@ 28913B7B2AC5AD1300E626F7 /* Category.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Category.swift; sourceTree = ""; }; 28913B7D2AC8033000E626F7 /* ToDo+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "ToDo+CoreDataClass.swift"; path = "ProjectManager/Model/ToDo/ToDo+CoreDataClass.swift"; sourceTree = ""; }; 28913B7E2AC8033000E626F7 /* ToDo+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "ToDo+CoreDataProperties.swift"; path = "ProjectManager/Model/ToDo/ToDo+CoreDataProperties.swift"; sourceTree = ""; }; + 28913B842AC83ACE00E626F7 /* ToDoDetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToDoDetailViewController.swift; sourceTree = ""; }; 28CA66E42AC0F72600FFD7A9 /* ListCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListCell.swift; sourceTree = ""; }; 28CA66E62AC0FE3100FFD7A9 /* ListHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListHeader.swift; sourceTree = ""; }; 28CA66EA2AC1805300FFD7A9 /* ListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListViewModel.swift; sourceTree = ""; }; @@ -94,6 +96,7 @@ C7431F0925F51E1D0094C4CF /* ListViewController.swift */, 28CA66E62AC0FE3100FFD7A9 /* ListHeader.swift */, 28CA66E42AC0F72600FFD7A9 /* ListCell.swift */, + 28913B842AC83ACE00E626F7 /* ToDoDetailViewController.swift */, 28DBCB952AC9292C00DFC58E /* InsetTextField.swift */, ); path = View; @@ -237,6 +240,7 @@ buildActionMask = 2147483647; files = ( 28913B732AC5666D00E626F7 /* TodoDateFormatter.swift in Sources */, + 28913B852AC83ACE00E626F7 /* ToDoDetailViewController.swift in Sources */, 28CA66ED2AC1808E00FFD7A9 /* Todo.swift in Sources */, 28DBCB962AC9292C00DFC58E /* InsetTextField.swift in Sources */, 28CA66E52AC0F72600FFD7A9 /* ListCell.swift in Sources */, diff --git a/ProjectManager/ProjectManager/View/ListViewController.swift b/ProjectManager/ProjectManager/View/ListViewController.swift index 60827368d..712061f35 100644 --- a/ProjectManager/ProjectManager/View/ListViewController.swift +++ b/ProjectManager/ProjectManager/View/ListViewController.swift @@ -46,7 +46,9 @@ final class ListViewController: UIViewController { @objc private func addTodo() { + let detailViewController = ToDoDetailViewController(isNew: true) + present(detailViewController, animated: true) } } diff --git a/ProjectManager/ProjectManager/View/ToDoDetailViewController.swift b/ProjectManager/ProjectManager/View/ToDoDetailViewController.swift new file mode 100644 index 000000000..e082ddec2 --- /dev/null +++ b/ProjectManager/ProjectManager/View/ToDoDetailViewController.swift @@ -0,0 +1,251 @@ +// +// ToDoDetailViewController.swift +// ProjectManager +// +// Created by Moon on 2023/09/30. +// + +import UIKit + +final class ToDoDetailViewController: UIViewController { + // MARK: - Title Bar property + private let leftButton: UIButton = { + let button = UIButton(type: .custom) + button.setTitle("Cancel", for: .normal) + button.setTitleColor(.systemBlue, for: .normal) + button.titleLabel?.adjustsFontForContentSizeCategory = true + button.titleLabel?.font = .preferredFont(forTextStyle: .title3) + + return button + }() + + private let titleLabel: UILabel = { + let label = UILabel() + label.text = Category.todo.rawValue + label.font = .preferredFont(for: .title3, weight: .bold) + label.adjustsFontForContentSizeCategory = true + + return label + }() + + private lazy var rightButton: UIButton = { + let button = UIButton(type: .custom) + button.setTitle("Done", for: .normal) + button.setTitleColor(.systemBlue, for: .normal) + button.titleLabel?.adjustsFontForContentSizeCategory = true + button.titleLabel?.font = .preferredFont(forTextStyle: .title3) + button.addAction(dismissAction(), for: .touchUpInside) + + return button + }() + + private let titleBarStackView: UIStackView = { + let stackView = UIStackView() + stackView.axis = .horizontal + stackView.distribution = .equalSpacing + stackView.alignment = .center + stackView.backgroundColor = .systemGray6 + stackView.layoutMargins = .init(top: 10, left: 20, bottom: 10, right: 20) + stackView.isLayoutMarginsRelativeArrangement = true + + return stackView + }() + + // MARK: - Content property + private let titleTextField: InsetTextField = { + let textField = InsetTextField() + textField.placeholder = "Title" + textField.font = .preferredFont(forTextStyle: .title3) + textField.backgroundColor = .systemBackground + textField.adjustsFontForContentSizeCategory = true + + return textField + }() + + private let datePicker: UIDatePicker = { + let datePicker = UIDatePicker() + datePicker.preferredDatePickerStyle = .wheels + datePicker.datePickerMode = .date + + return datePicker + }() + + private let bodyTextView: UITextView = { + let textView = UITextView() + textView.font = .preferredFont(forTextStyle: .title3) + textView.adjustsFontForContentSizeCategory = true + + return textView + }() + + private let contentStackView: UIStackView = { + let stackView = UIStackView() + stackView.axis = .vertical + stackView.spacing = 20 + stackView.layoutMargins = .init(top: 10, left: 20, bottom: 30, right: 20) + stackView.isLayoutMarginsRelativeArrangement = true + + return stackView + }() + + private let titleShadowView: UIView = { + let view = UIView() + view.layer.shadowOpacity = 0.5 + view.layer.shadowOffset = CGSize(width: .zero, height: 3) + view.layer.shadowRadius = 3 + + return view + }() + + private let bodyShadowView: UIView = { + let view = UIView() + view.layer.shadowOpacity = 0.5 + view.layer.shadowOffset = CGSize(width: .zero, height: 3) + view.layer.shadowRadius = 3 + + return view + }() + + init(isNew: Bool) { + super.init(nibName: nil, bundle: nil) + + configureUI() + setUpDelegates() + setUpLeftButton(isNew: isNew) + setUpEditableContent(isNew: isNew) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func setUpDelegates() { + bodyTextView.delegate = self + } + + private func setUpLeftButton(isNew: Bool) { + if isNew { + leftButton.setTitle("Cancel", for: .normal) + leftButton.addAction(dismissAction(), for: .touchUpInside) + } else { + leftButton.setTitle("Edit", for: .normal) + leftButton.addAction(enableEditContentAction(), for: .touchUpInside) + } + } + + private func dismissAction() -> UIAction { + return UIAction { _ in + self.dismiss(animated: true) + } + } + + private func enableEditContentAction() -> UIAction { + return UIAction { [weak self] _ in + [self?.titleTextField, self?.datePicker, self?.bodyTextView].forEach { + $0?.isUserInteractionEnabled = true + $0?.backgroundColor = .systemBackground + } + } + } + + private func setUpEditableContent(isNew: Bool) { + if isNew == false { + [titleTextField, datePicker, bodyTextView].forEach { + $0.isUserInteractionEnabled = false + $0.backgroundColor = .systemGray6 + } + } + } +} + +// MARK: - Configure UI +extension ToDoDetailViewController { + private func configureUI() { + setUpView() + addSubviews() + setUpConstraints() + } + + private func setUpView() { + view.backgroundColor = .systemBackground + } + + private func addSubviews() { + [leftButton, titleLabel, rightButton].forEach { + titleBarStackView.addArrangedSubview($0) + } + + titleShadowView.addSubview(titleTextField) + bodyShadowView.addSubview(bodyTextView) + + [titleShadowView, datePicker, bodyShadowView].forEach { + contentStackView.addArrangedSubview($0) + } + + view.addSubview(titleBarStackView) + view.addSubview(contentStackView) + } + + private func setUpConstraints() { + setUpTitleBarStackViewConstraints() + setUpTitleTextFieldConstraints() + setUpBodyTextViewConstraints() + setUpContentStackViewConstraints() + } + + private func setUpTitleBarStackViewConstraints() { + titleBarStackView.translatesAutoresizingMaskIntoConstraints = false + + NSLayoutConstraint.activate([ + titleBarStackView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor), + titleBarStackView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor), + titleBarStackView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor) + ]) + } + + private func setUpTitleTextFieldConstraints() { + titleTextField.translatesAutoresizingMaskIntoConstraints = false + + NSLayoutConstraint.activate([ + titleTextField.leadingAnchor.constraint(equalTo: titleShadowView.leadingAnchor), + titleTextField.trailingAnchor.constraint(equalTo: titleShadowView.trailingAnchor), + titleTextField.topAnchor.constraint(equalTo: titleShadowView.topAnchor), + titleTextField.bottomAnchor.constraint(equalTo: titleShadowView.bottomAnchor) + ]) + } + + private func setUpBodyTextViewConstraints() { + bodyTextView.translatesAutoresizingMaskIntoConstraints = false + + NSLayoutConstraint.activate([ + bodyTextView.leadingAnchor.constraint(equalTo: bodyShadowView.leadingAnchor), + bodyTextView.trailingAnchor.constraint(equalTo: bodyShadowView.trailingAnchor), + bodyTextView.topAnchor.constraint(equalTo: bodyShadowView.topAnchor), + bodyTextView.bottomAnchor.constraint(equalTo: bodyShadowView.bottomAnchor) + ]) + } + + private func setUpContentStackViewConstraints() { + contentStackView.translatesAutoresizingMaskIntoConstraints = false + + NSLayoutConstraint.activate([ + contentStackView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor), + contentStackView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor), + contentStackView.topAnchor.constraint(equalTo: titleBarStackView.bottomAnchor), + contentStackView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor) + ]) + } +} + +// MARK: - UITextViewDelegate +extension ToDoDetailViewController: UITextViewDelegate { + func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool { + guard let originalText = textView.text else { + return true + } + + let newLength = originalText.count + text.count - range.length + + return newLength <= 1000 + } +} From 1b347a12cc3c61f9962be38aa03d4d65589f2a18 Mon Sep 17 00:00:00 2001 From: Moon <1tjen@naver.com> Date: Sun, 1 Oct 2023 16:55:17 +0900 Subject: [PATCH 33/48] =?UTF-8?q?feat:=20Cell=EC=9D=84=20=EC=84=A0?= =?UTF-8?q?=ED=83=9D=ED=96=88=EC=9D=84=20=EB=95=8C=20=EC=84=A0=ED=83=9D=20?= =?UTF-8?q?=ED=91=9C=EC=8B=9C=EA=B0=80=20=EC=82=AC=EB=9D=BC=EC=A7=80?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ProjectManager/ProjectManager/View/ListViewController.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ProjectManager/ProjectManager/View/ListViewController.swift b/ProjectManager/ProjectManager/View/ListViewController.swift index 712061f35..2804cc2a9 100644 --- a/ProjectManager/ProjectManager/View/ListViewController.swift +++ b/ProjectManager/ProjectManager/View/ListViewController.swift @@ -125,5 +125,7 @@ extension ListViewController: UITableViewDataSource { // MARK: - Table View Delegate extension ListViewController: UITableViewDelegate { - + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + tableView.deselectRow(at: indexPath, animated: false) + } } From 51ce58bfbba5e4894d0f9e1e2bd807a05abac303 Mon Sep 17 00:00:00 2001 From: Moon <1tjen@naver.com> Date: Mon, 2 Oct 2023 02:33:57 +0900 Subject: [PATCH 34/48] =?UTF-8?q?feat:=20DataManager=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ProjectManager.xcodeproj/project.pbxproj | 4 ++ .../Model/ToDo/DataManager.swift | 66 +++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 ProjectManager/ProjectManager/Model/ToDo/DataManager.swift diff --git a/ProjectManager/ProjectManager.xcodeproj/project.pbxproj b/ProjectManager/ProjectManager.xcodeproj/project.pbxproj index 87ffab521..db790cfc4 100644 --- a/ProjectManager/ProjectManager.xcodeproj/project.pbxproj +++ b/ProjectManager/ProjectManager.xcodeproj/project.pbxproj @@ -13,6 +13,7 @@ 28913B7C2AC5AD1300E626F7 /* Category.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28913B7B2AC5AD1300E626F7 /* Category.swift */; }; 28913B7F2AC8033000E626F7 /* ToDo+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28913B7D2AC8033000E626F7 /* ToDo+CoreDataClass.swift */; }; 28913B802AC8033000E626F7 /* ToDo+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28913B7E2AC8033000E626F7 /* ToDo+CoreDataProperties.swift */; }; + 28913B832AC8114F00E626F7 /* DataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28913B822AC8114F00E626F7 /* DataManager.swift */; }; 28913B852AC83ACE00E626F7 /* ToDoDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28913B842AC83ACE00E626F7 /* ToDoDetailViewController.swift */; }; 28CA66E52AC0F72600FFD7A9 /* ListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28CA66E42AC0F72600FFD7A9 /* ListCell.swift */; }; 28CA66E72AC0FE3100FFD7A9 /* ListHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28CA66E62AC0FE3100FFD7A9 /* ListHeader.swift */; }; @@ -35,6 +36,7 @@ 28913B7B2AC5AD1300E626F7 /* Category.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Category.swift; sourceTree = ""; }; 28913B7D2AC8033000E626F7 /* ToDo+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "ToDo+CoreDataClass.swift"; path = "ProjectManager/Model/ToDo/ToDo+CoreDataClass.swift"; sourceTree = ""; }; 28913B7E2AC8033000E626F7 /* ToDo+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "ToDo+CoreDataProperties.swift"; path = "ProjectManager/Model/ToDo/ToDo+CoreDataProperties.swift"; sourceTree = ""; }; + 28913B822AC8114F00E626F7 /* DataManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataManager.swift; sourceTree = ""; }; 28913B842AC83ACE00E626F7 /* ToDoDetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToDoDetailViewController.swift; sourceTree = ""; }; 28CA66E42AC0F72600FFD7A9 /* ListCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListCell.swift; sourceTree = ""; }; 28CA66E62AC0FE3100FFD7A9 /* ListHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListHeader.swift; sourceTree = ""; }; @@ -77,6 +79,7 @@ children = ( 28913B782AC5AC9A00E626F7 /* ToDo.xcdatamodeld */, 28913B7B2AC5AD1300E626F7 /* Category.swift */, + 28913B822AC8114F00E626F7 /* DataManager.swift */, ); path = ToDo; sourceTree = ""; @@ -241,6 +244,7 @@ files = ( 28913B732AC5666D00E626F7 /* TodoDateFormatter.swift in Sources */, 28913B852AC83ACE00E626F7 /* ToDoDetailViewController.swift in Sources */, + 28913B832AC8114F00E626F7 /* DataManager.swift in Sources */, 28CA66ED2AC1808E00FFD7A9 /* Todo.swift in Sources */, 28DBCB962AC9292C00DFC58E /* InsetTextField.swift in Sources */, 28CA66E52AC0F72600FFD7A9 /* ListCell.swift in Sources */, diff --git a/ProjectManager/ProjectManager/Model/ToDo/DataManager.swift b/ProjectManager/ProjectManager/Model/ToDo/DataManager.swift new file mode 100644 index 000000000..0c2d85257 --- /dev/null +++ b/ProjectManager/ProjectManager/Model/ToDo/DataManager.swift @@ -0,0 +1,66 @@ +// +// DataManager.swift +// ProjectManager +// +// Created by Moon on 2023/09/30. +// + +import CoreData + +final class DataManager { + private let persistentContainer: NSPersistentContainer = { + let container = NSPersistentContainer(name: "ToDo") + + container.loadPersistentStores { storeDescription, error in + if let error = error as NSError? { + fatalError("Unresolved error \(error), \(error.userInfo)") + } + } + + return container + }() + + private let viewContext: NSManagedObjectContext + + init() { + viewContext = persistentContainer.viewContext + } + + func createToDo(title: String? = "", deadline: Date? = Date(), body: String? = "", category: Category) -> ToDo { + let todo = ToDo(context: viewContext) + todo.title = title + todo.deadline = deadline + todo.body = body + todo.category = category.rawValue + + return todo + } + + func fetchToDoList() -> [ToDo] { + do { + let fetchRequest = ToDo.fetchRequest() + + return try viewContext.fetch(fetchRequest) + } catch let error as NSError { + print("Unresolved error: \(error), \(error.userInfo)") + + return [] + } + } + + func saveContext() { + guard viewContext.hasChanges else { + return + } + + do { + try viewContext.save() + } catch let error as NSError { + print("Unresolved error: \(error), \(error.userInfo)") + } + } + + func deleteItem(_ todo: ToDo) { + viewContext.delete(todo) + } +} From 9893c7e760120f02f0ae16e6ab81909f4560e156 Mon Sep 17 00:00:00 2001 From: Moon <1tjen@naver.com> Date: Mon, 2 Oct 2023 02:42:48 +0900 Subject: [PATCH 35/48] =?UTF-8?q?feat:=20CoreData=EB=A5=BC=20=ED=99=9C?= =?UTF-8?q?=EC=9A=A9=ED=95=98=EB=8F=84=EB=A1=9D=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ListViewModel: dataManager추가, 데이터 로드, ToDoDetailDone 알림을 받아 저장 수행 ListCell: ToDo 타입을 받아 셀에 표시하도록 수정 ListViewController: 추가 버튼을 눌렀을 때 ToDo 타입도 전달하도록 수정, Cell을 선택했을 때 ToDoDetailViewController가 보이도록 기능 추가 ToDoDetailViewController: ToDo 타입을 받아 내용을 표시하는 기능 추가, Done버튼을 눌렀을 때 todo를 코어데이터의 컨텍스트에 올리고 Notification으로 뷰모델에 알리는 기능 추가 --- .../ProjectManager/View/ListCell.swift | 9 ++-- .../View/ListViewController.swift | 11 ++++- .../View/ToDoDetailViewController.swift | 44 +++++++++++++++++-- .../ViewModel/ListViewModel.swift | 31 +++++++++---- 4 files changed, 77 insertions(+), 18 deletions(-) diff --git a/ProjectManager/ProjectManager/View/ListCell.swift b/ProjectManager/ProjectManager/View/ListCell.swift index 4efbbaf29..676b1ae5b 100644 --- a/ProjectManager/ProjectManager/View/ListCell.swift +++ b/ProjectManager/ProjectManager/View/ListCell.swift @@ -64,12 +64,13 @@ final class ListCell: UITableViewCell { ) } - func setUpContent(_ todo: Todo) { + func setUpContent(_ todo: ToDo) { titleLabel.text = todo.title - descriptionLabel.text = todo.description + descriptionLabel.text = todo.body deadlineLabel.text = TodoDateFormatter.string( - from: todo.deadline, - format: DateFormat.todo) + from: todo.deadline ?? Date(), + format: DateFormat.todo + ) } } diff --git a/ProjectManager/ProjectManager/View/ListViewController.swift b/ProjectManager/ProjectManager/View/ListViewController.swift index 2804cc2a9..3b2663d3a 100644 --- a/ProjectManager/ProjectManager/View/ListViewController.swift +++ b/ProjectManager/ProjectManager/View/ListViewController.swift @@ -46,7 +46,8 @@ final class ListViewController: UIViewController { @objc private func addTodo() { - let detailViewController = ToDoDetailViewController(isNew: true) + let todo = listViewModel.dataManager.createToDo(category: .todo) + let detailViewController = ToDoDetailViewController(isNew: true, todo: todo) present(detailViewController, animated: true) } @@ -127,5 +128,13 @@ extension ListViewController: UITableViewDataSource { extension ListViewController: UITableViewDelegate { func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: false) + + guard let todo = listViewModel.todoList?[safe: indexPath.row] else { + return + } + + let detailViewController = ToDoDetailViewController(isNew: false, todo: todo) + + present(detailViewController, animated: true) } } diff --git a/ProjectManager/ProjectManager/View/ToDoDetailViewController.swift b/ProjectManager/ProjectManager/View/ToDoDetailViewController.swift index e082ddec2..68e4b9eb4 100644 --- a/ProjectManager/ProjectManager/View/ToDoDetailViewController.swift +++ b/ProjectManager/ProjectManager/View/ToDoDetailViewController.swift @@ -34,7 +34,7 @@ final class ToDoDetailViewController: UIViewController { button.setTitleColor(.systemBlue, for: .normal) button.titleLabel?.adjustsFontForContentSizeCategory = true button.titleLabel?.font = .preferredFont(forTextStyle: .title3) - button.addAction(dismissAction(), for: .touchUpInside) + button.addAction(doneAction(), for: .touchUpInside) return button }() @@ -106,13 +106,19 @@ final class ToDoDetailViewController: UIViewController { return view }() - init(isNew: Bool) { + private let todo: ToDo + + init(isNew: Bool, todo: ToDo) { + self.todo = todo + super.init(nibName: nil, bundle: nil) configureUI() setUpDelegates() setUpLeftButton(isNew: isNew) setUpEditableContent(isNew: isNew) + setUpToDoDetailView(todo) + setUpTitle(todo: todo) } required init?(coder: NSCoder) { @@ -133,9 +139,23 @@ final class ToDoDetailViewController: UIViewController { } } + private func doneAction() -> UIAction { + return UIAction { [weak self] _ in + self?.updateToDo() + + NotificationCenter.default + .post( + name: NSNotification.Name("ToDoDetailDone"), + object: nil + ) + + self?.dismiss(animated: true) + } + } + private func dismissAction() -> UIAction { - return UIAction { _ in - self.dismiss(animated: true) + return UIAction { [weak self] _ in + self?.dismiss(animated: true) } } @@ -156,6 +176,22 @@ final class ToDoDetailViewController: UIViewController { } } } + + private func setUpToDoDetailView(_ todo: ToDo) { + titleTextField.text = todo.title + datePicker.date = todo.deadline ?? Date() + bodyTextView.text = todo.body + } + + private func setUpTitle(todo: ToDo) { + titleLabel.text = todo.category + } + + private func updateToDo() { + todo.title = titleTextField.text + todo.deadline = datePicker.date + todo.body = bodyTextView.text + } } // MARK: - Configure UI diff --git a/ProjectManager/ProjectManager/ViewModel/ListViewModel.swift b/ProjectManager/ProjectManager/ViewModel/ListViewModel.swift index 24e5f3774..4fdcad6b6 100644 --- a/ProjectManager/ProjectManager/ViewModel/ListViewModel.swift +++ b/ProjectManager/ProjectManager/ViewModel/ListViewModel.swift @@ -8,10 +8,14 @@ import Foundation final class ListViewModel { + let dataManager = DataManager() + // Model이 변경되면 ListViewController의 테이블뷰를 업데이트 - var todoList: [Todo]? { + // ListHeader에 표시되는 count도 업데이트 + var todoList: [ToDo]? { didSet { bindTodoList?() + count = String(todoList?.count ?? 0) } } @@ -35,6 +39,7 @@ final class ListViewModel { } // ListViewController의 viewDidLoad 시점을 받아 모델을 로드할 수 있도록 함 + // ToDoDetailViewController의 Done 시점을 받아 dataManager에서 저장 수행 private func setUpNotifications() { NotificationCenter.default .addObserver( @@ -43,18 +48,26 @@ final class ListViewModel { name: NSNotification.Name("ListViewControllerViewDidLoad"), object: nil ) + NotificationCenter.default + .addObserver( + self, + selector: #selector(saveTodo), + name: NSNotification.Name("ToDoDetailDone"), + object: nil + ) } + // ListViewController의 viewDidLoad 시점을 받아 모델을 로드할 수 있도록 함 @objc private func loadTodoList() { - todoList = [ - Todo(title: "1", description: "111", deadline: Date()), - Todo(title: "2", description: "222", deadline: Date()), - Todo(title: "3", description: "333", deadline: Date()), - Todo(title: "4", description: "444", deadline: Date()), - Todo(title: "5", description: "555", deadline: Date()), - Todo(title: "6", description: "666", deadline: Date()) - ] + todoList = dataManager.fetchToDoList() + } + + // ToDoDetailViewController의 Done 시점을 받아 dataManager에서 저장 수행 + @objc + private func saveTodo() { + dataManager.saveContext() + loadTodoList() } func bindCount(_ handler: @escaping (ListViewModel) -> Void) { From 24548ca724c2c175cdf7f3cbb99af225783cb78e Mon Sep 17 00:00:00 2001 From: Moon <1tjen@naver.com> Date: Mon, 2 Oct 2023 02:52:24 +0900 Subject: [PATCH 36/48] =?UTF-8?q?chore:=20ToDoDetailViewController=20?= =?UTF-8?q?=EC=A3=BC=EC=84=9D=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../View/ToDoDetailViewController.swift | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/ProjectManager/ProjectManager/View/ToDoDetailViewController.swift b/ProjectManager/ProjectManager/View/ToDoDetailViewController.swift index 68e4b9eb4..1760a56e4 100644 --- a/ProjectManager/ProjectManager/View/ToDoDetailViewController.swift +++ b/ProjectManager/ProjectManager/View/ToDoDetailViewController.swift @@ -34,7 +34,7 @@ final class ToDoDetailViewController: UIViewController { button.setTitleColor(.systemBlue, for: .normal) button.titleLabel?.adjustsFontForContentSizeCategory = true button.titleLabel?.font = .preferredFont(forTextStyle: .title3) - button.addAction(doneAction(), for: .touchUpInside) + button.addAction(doneAction(), for: .touchUpInside)// todo를 업데이트하고 뷰모델에 알림 전송 후 dismiss return button }() @@ -129,16 +129,18 @@ final class ToDoDetailViewController: UIViewController { bodyTextView.delegate = self } + // 추가 버튼으로 새로 만들어졌을 때는 Cancel, 기존에 있던 todo일 때는 Edit 기능 수행 private func setUpLeftButton(isNew: Bool) { if isNew { leftButton.setTitle("Cancel", for: .normal) - leftButton.addAction(dismissAction(), for: .touchUpInside) + leftButton.addAction(dismissAction(), for: .touchUpInside)// 저장 없이 dismiss만 수행 } else { leftButton.setTitle("Edit", for: .normal) - leftButton.addAction(enableEditContentAction(), for: .touchUpInside) + leftButton.addAction(enableEditContentAction(), for: .touchUpInside)// 유저와 상호작용이 가능하도록 하고 배경색 변경 } } + // todo를 업데이트하고 뷰모델에 알림 전송 후 dismiss private func doneAction() -> UIAction { return UIAction { [weak self] _ in self?.updateToDo() @@ -152,13 +154,14 @@ final class ToDoDetailViewController: UIViewController { self?.dismiss(animated: true) } } - + // 저장 없이 dismiss만 수행 private func dismissAction() -> UIAction { return UIAction { [weak self] _ in self?.dismiss(animated: true) } } + // 유저와 상호작용이 가능하도록 하고 배경색 변경 private func enableEditContentAction() -> UIAction { return UIAction { [weak self] _ in [self?.titleTextField, self?.datePicker, self?.bodyTextView].forEach { @@ -168,6 +171,7 @@ final class ToDoDetailViewController: UIViewController { } } + // 기존의 todo일 경우 유저와 상호작용 불가능하고 배경색은 회색 private func setUpEditableContent(isNew: Bool) { if isNew == false { [titleTextField, datePicker, bodyTextView].forEach { From b501c8fb1c55c6ae16b7caea2a7761ffe05eed5b Mon Sep 17 00:00:00 2001 From: Moon <1tjen@naver.com> Date: Mon, 2 Oct 2023 02:53:24 +0900 Subject: [PATCH 37/48] =?UTF-8?q?refactor:=20=EB=8D=94=20=EC=9D=B4?= =?UTF-8?q?=EC=83=81=20=EC=82=AC=EC=9A=A9=ED=95=98=EC=A7=80=20=EC=95=8A?= =?UTF-8?q?=EB=8A=94=20Todo=20=ED=83=80=EC=9E=85=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ProjectManager.xcodeproj/project.pbxproj | 4 ---- ProjectManager/ProjectManager/Model/Todo.swift | 14 -------------- 2 files changed, 18 deletions(-) delete mode 100644 ProjectManager/ProjectManager/Model/Todo.swift diff --git a/ProjectManager/ProjectManager.xcodeproj/project.pbxproj b/ProjectManager/ProjectManager.xcodeproj/project.pbxproj index db790cfc4..5a0442638 100644 --- a/ProjectManager/ProjectManager.xcodeproj/project.pbxproj +++ b/ProjectManager/ProjectManager.xcodeproj/project.pbxproj @@ -18,7 +18,6 @@ 28CA66E52AC0F72600FFD7A9 /* ListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28CA66E42AC0F72600FFD7A9 /* ListCell.swift */; }; 28CA66E72AC0FE3100FFD7A9 /* ListHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28CA66E62AC0FE3100FFD7A9 /* ListHeader.swift */; }; 28CA66EB2AC1805300FFD7A9 /* ListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28CA66EA2AC1805300FFD7A9 /* ListViewModel.swift */; }; - 28CA66ED2AC1808E00FFD7A9 /* Todo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28CA66EC2AC1808E00FFD7A9 /* Todo.swift */; }; 28CA66F22AC1A08600FFD7A9 /* Reusable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28CA66F12AC1A08600FFD7A9 /* Reusable.swift */; }; 28DBCB962AC9292C00DFC58E /* InsetTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28DBCB952AC9292C00DFC58E /* InsetTextField.swift */; }; 28DBCB982AC94A5F00DFC58E /* UIFont+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28DBCB972AC94A5F00DFC58E /* UIFont+.swift */; }; @@ -41,7 +40,6 @@ 28CA66E42AC0F72600FFD7A9 /* ListCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListCell.swift; sourceTree = ""; }; 28CA66E62AC0FE3100FFD7A9 /* ListHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListHeader.swift; sourceTree = ""; }; 28CA66EA2AC1805300FFD7A9 /* ListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListViewModel.swift; sourceTree = ""; }; - 28CA66EC2AC1808E00FFD7A9 /* Todo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Todo.swift; sourceTree = ""; }; 28CA66F12AC1A08600FFD7A9 /* Reusable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Reusable.swift; sourceTree = ""; }; 28DBCB952AC9292C00DFC58E /* InsetTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InsetTextField.swift; sourceTree = ""; }; 28DBCB972AC94A5F00DFC58E /* UIFont+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIFont+.swift"; sourceTree = ""; }; @@ -109,7 +107,6 @@ isa = PBXGroup; children = ( 28913B772AC5AC3100E626F7 /* ToDo */, - 28CA66EC2AC1808E00FFD7A9 /* Todo.swift */, ); path = Model; sourceTree = ""; @@ -245,7 +242,6 @@ 28913B732AC5666D00E626F7 /* TodoDateFormatter.swift in Sources */, 28913B852AC83ACE00E626F7 /* ToDoDetailViewController.swift in Sources */, 28913B832AC8114F00E626F7 /* DataManager.swift in Sources */, - 28CA66ED2AC1808E00FFD7A9 /* Todo.swift in Sources */, 28DBCB962AC9292C00DFC58E /* InsetTextField.swift in Sources */, 28CA66E52AC0F72600FFD7A9 /* ListCell.swift in Sources */, 28913B802AC8033000E626F7 /* ToDo+CoreDataProperties.swift in Sources */, diff --git a/ProjectManager/ProjectManager/Model/Todo.swift b/ProjectManager/ProjectManager/Model/Todo.swift deleted file mode 100644 index 11a158901..000000000 --- a/ProjectManager/ProjectManager/Model/Todo.swift +++ /dev/null @@ -1,14 +0,0 @@ -// -// Todo.swift -// ProjectManager -// -// Created by Moon on 2023/09/25. -// - -import Foundation - -struct Todo { - var title: String - var description: String - var deadline: Date -} From 06f65e3739aba393d407d02ffdf842ec913a19b6 Mon Sep 17 00:00:00 2001 From: Moon <1tjen@naver.com> Date: Mon, 2 Oct 2023 15:12:17 +0900 Subject: [PATCH 38/48] =?UTF-8?q?feat:=20ToDoDetailViewController=EC=9D=98?= =?UTF-8?q?=20viewWillDisappear=EC=97=90=EC=84=9C=20=EC=A0=80=EC=9E=A5=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../View/ToDoDetailViewController.swift | 28 +++++++++++++------ 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/ProjectManager/ProjectManager/View/ToDoDetailViewController.swift b/ProjectManager/ProjectManager/View/ToDoDetailViewController.swift index 1760a56e4..4b84fa68f 100644 --- a/ProjectManager/ProjectManager/View/ToDoDetailViewController.swift +++ b/ProjectManager/ProjectManager/View/ToDoDetailViewController.swift @@ -125,6 +125,12 @@ final class ToDoDetailViewController: UIViewController { fatalError("init(coder:) has not been implemented") } + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + + doneToDoDetail() + } + private func setUpDelegates() { bodyTextView.delegate = self } @@ -143,14 +149,7 @@ final class ToDoDetailViewController: UIViewController { // todo를 업데이트하고 뷰모델에 알림 전송 후 dismiss private func doneAction() -> UIAction { return UIAction { [weak self] _ in - self?.updateToDo() - - NotificationCenter.default - .post( - name: NSNotification.Name("ToDoDetailDone"), - object: nil - ) - + self?.doneToDoDetail() self?.dismiss(animated: true) } } @@ -196,6 +195,19 @@ final class ToDoDetailViewController: UIViewController { todo.deadline = datePicker.date todo.body = bodyTextView.text } + + private func postToDoDetailDone() { + NotificationCenter.default + .post( + name: NSNotification.Name("ToDoDetailDone"), + object: nil + ) + } + + private func doneToDoDetail() { + updateToDo() + postToDoDetailDone() + } } // MARK: - Configure UI From 4365f4f53fe52a8ea05e3f88f456210dffb4c663 Mon Sep 17 00:00:00 2001 From: Moon <1tjen@naver.com> Date: Tue, 3 Oct 2023 22:17:35 +0900 Subject: [PATCH 39/48] =?UTF-8?q?chore:=20LaunchScreen=20=ED=8F=B4?= =?UTF-8?q?=EB=8D=94=20=EC=9C=84=EC=B9=98=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit View -> Resource --- ProjectManager/ProjectManager.xcodeproj/project.pbxproj | 2 +- .../{View => Resource}/Base.lproj/LaunchScreen.storyboard | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename ProjectManager/ProjectManager/{View => Resource}/Base.lproj/LaunchScreen.storyboard (100%) diff --git a/ProjectManager/ProjectManager.xcodeproj/project.pbxproj b/ProjectManager/ProjectManager.xcodeproj/project.pbxproj index 5a0442638..fc1e16830 100644 --- a/ProjectManager/ProjectManager.xcodeproj/project.pbxproj +++ b/ProjectManager/ProjectManager.xcodeproj/project.pbxproj @@ -93,7 +93,6 @@ 2897D3762AC05F8D00662BAA /* View */ = { isa = PBXGroup; children = ( - C7431F1025F51E1E0094C4CF /* LaunchScreen.storyboard */, C7431F0925F51E1D0094C4CF /* ListViewController.swift */, 28CA66E62AC0FE3100FFD7A9 /* ListHeader.swift */, 28CA66E42AC0F72600FFD7A9 /* ListCell.swift */, @@ -123,6 +122,7 @@ 2897D3792AC05FBE00662BAA /* Resource */ = { isa = PBXGroup; children = ( + C7431F1025F51E1E0094C4CF /* LaunchScreen.storyboard */, C7431F0E25F51E1E0094C4CF /* Assets.xcassets */, ); path = Resource; diff --git a/ProjectManager/ProjectManager/View/Base.lproj/LaunchScreen.storyboard b/ProjectManager/ProjectManager/Resource/Base.lproj/LaunchScreen.storyboard similarity index 100% rename from ProjectManager/ProjectManager/View/Base.lproj/LaunchScreen.storyboard rename to ProjectManager/ProjectManager/Resource/Base.lproj/LaunchScreen.storyboard From 7a557461f6ad1333f8ed44dac44e6bff59e3f888 Mon Sep 17 00:00:00 2001 From: Moon <1tjen@naver.com> Date: Tue, 3 Oct 2023 22:22:02 +0900 Subject: [PATCH 40/48] =?UTF-8?q?chore:=20ToDo=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 폴더 구조에는 Model/ToDo 안에 있지만 프로젝트에서는 최상단에 있었기 때문에 프로젝트에서도 Model/ToDo 내부로 이동 --- .../ProjectManager.xcodeproj/project.pbxproj | 12 ++- .../xcschemes/ProjectManager.xcscheme | 77 +++++++++++++++++++ 2 files changed, 85 insertions(+), 4 deletions(-) create mode 100644 ProjectManager/ProjectManager.xcodeproj/xcshareddata/xcschemes/ProjectManager.xcscheme diff --git a/ProjectManager/ProjectManager.xcodeproj/project.pbxproj b/ProjectManager/ProjectManager.xcodeproj/project.pbxproj index fc1e16830..1abc48d8c 100644 --- a/ProjectManager/ProjectManager.xcodeproj/project.pbxproj +++ b/ProjectManager/ProjectManager.xcodeproj/project.pbxproj @@ -21,6 +21,7 @@ 28CA66F22AC1A08600FFD7A9 /* Reusable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28CA66F12AC1A08600FFD7A9 /* Reusable.swift */; }; 28DBCB962AC9292C00DFC58E /* InsetTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28DBCB952AC9292C00DFC58E /* InsetTextField.swift */; }; 28DBCB982AC94A5F00DFC58E /* UIFont+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28DBCB972AC94A5F00DFC58E /* UIFont+.swift */; }; + 28DBCB9A2ACAD19500DFC58E /* ToDoDetailViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28DBCB992ACAD19500DFC58E /* ToDoDetailViewModel.swift */; }; C7431F0625F51E1D0094C4CF /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7431F0525F51E1D0094C4CF /* AppDelegate.swift */; }; C7431F0825F51E1D0094C4CF /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7431F0725F51E1D0094C4CF /* SceneDelegate.swift */; }; C7431F0A25F51E1D0094C4CF /* ListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7431F0925F51E1D0094C4CF /* ListViewController.swift */; }; @@ -33,8 +34,8 @@ 28913B752AC5950E00E626F7 /* Array+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array+.swift"; sourceTree = ""; }; 28913B792AC5AC9A00E626F7 /* ToDoModel.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = ToDoModel.xcdatamodel; sourceTree = ""; }; 28913B7B2AC5AD1300E626F7 /* Category.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Category.swift; sourceTree = ""; }; - 28913B7D2AC8033000E626F7 /* ToDo+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "ToDo+CoreDataClass.swift"; path = "ProjectManager/Model/ToDo/ToDo+CoreDataClass.swift"; sourceTree = ""; }; - 28913B7E2AC8033000E626F7 /* ToDo+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "ToDo+CoreDataProperties.swift"; path = "ProjectManager/Model/ToDo/ToDo+CoreDataProperties.swift"; sourceTree = ""; }; + 28913B7D2AC8033000E626F7 /* ToDo+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ToDo+CoreDataClass.swift"; sourceTree = ""; }; + 28913B7E2AC8033000E626F7 /* ToDo+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ToDo+CoreDataProperties.swift"; sourceTree = ""; }; 28913B822AC8114F00E626F7 /* DataManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataManager.swift; sourceTree = ""; }; 28913B842AC83ACE00E626F7 /* ToDoDetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToDoDetailViewController.swift; sourceTree = ""; }; 28CA66E42AC0F72600FFD7A9 /* ListCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListCell.swift; sourceTree = ""; }; @@ -43,6 +44,7 @@ 28CA66F12AC1A08600FFD7A9 /* Reusable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Reusable.swift; sourceTree = ""; }; 28DBCB952AC9292C00DFC58E /* InsetTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InsetTextField.swift; sourceTree = ""; }; 28DBCB972AC94A5F00DFC58E /* UIFont+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIFont+.swift"; sourceTree = ""; }; + 28DBCB992ACAD19500DFC58E /* ToDoDetailViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToDoDetailViewModel.swift; sourceTree = ""; }; C7431F0225F51E1D0094C4CF /* ProjectManager.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ProjectManager.app; sourceTree = BUILT_PRODUCTS_DIR; }; C7431F0525F51E1D0094C4CF /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; C7431F0725F51E1D0094C4CF /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; @@ -76,6 +78,8 @@ isa = PBXGroup; children = ( 28913B782AC5AC9A00E626F7 /* ToDo.xcdatamodeld */, + 28913B7D2AC8033000E626F7 /* ToDo+CoreDataClass.swift */, + 28913B7E2AC8033000E626F7 /* ToDo+CoreDataProperties.swift */, 28913B7B2AC5AD1300E626F7 /* Category.swift */, 28913B822AC8114F00E626F7 /* DataManager.swift */, ); @@ -86,6 +90,7 @@ isa = PBXGroup; children = ( 28CA66EA2AC1805300FFD7A9 /* ListViewModel.swift */, + 28DBCB992ACAD19500DFC58E /* ToDoDetailViewModel.swift */, ); path = ViewModel; sourceTree = ""; @@ -141,8 +146,6 @@ C7431EF925F51E1D0094C4CF = { isa = PBXGroup; children = ( - 28913B7D2AC8033000E626F7 /* ToDo+CoreDataClass.swift */, - 28913B7E2AC8033000E626F7 /* ToDo+CoreDataProperties.swift */, C7431F0425F51E1D0094C4CF /* ProjectManager */, C7431F0325F51E1D0094C4CF /* Products */, ); @@ -248,6 +251,7 @@ 28CA66F22AC1A08600FFD7A9 /* Reusable.swift in Sources */, C7431F0A25F51E1D0094C4CF /* ListViewController.swift in Sources */, 28CA66EB2AC1805300FFD7A9 /* ListViewModel.swift in Sources */, + 28DBCB9A2ACAD19500DFC58E /* ToDoDetailViewModel.swift in Sources */, 28DBCB982AC94A5F00DFC58E /* UIFont+.swift in Sources */, C7431F0625F51E1D0094C4CF /* AppDelegate.swift in Sources */, 28CA66E72AC0FE3100FFD7A9 /* ListHeader.swift in Sources */, diff --git a/ProjectManager/ProjectManager.xcodeproj/xcshareddata/xcschemes/ProjectManager.xcscheme b/ProjectManager/ProjectManager.xcodeproj/xcshareddata/xcschemes/ProjectManager.xcscheme new file mode 100644 index 000000000..c4576fde4 --- /dev/null +++ b/ProjectManager/ProjectManager.xcodeproj/xcshareddata/xcschemes/ProjectManager.xcscheme @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From c1af24f7a6d3b44d8b07aa8d35645cf9631371dd Mon Sep 17 00:00:00 2001 From: Moon <1tjen@naver.com> Date: Tue, 3 Oct 2023 22:22:57 +0900 Subject: [PATCH 41/48] =?UTF-8?q?refactor:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20=EC=BD=94=EB=93=9C=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ProjectManager/ProjectManager/View/InsetTextField.swift | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/ProjectManager/ProjectManager/View/InsetTextField.swift b/ProjectManager/ProjectManager/View/InsetTextField.swift index 4956df389..ab06b9cfa 100644 --- a/ProjectManager/ProjectManager/View/InsetTextField.swift +++ b/ProjectManager/ProjectManager/View/InsetTextField.swift @@ -19,13 +19,6 @@ final class InsetTextField: UITextField { } override func editingRect(forBounds bounds: CGRect) -> CGRect { - let editingInsets = UIEdgeInsets( - top: inset.top, - left: inset.left, - bottom: inset.bottom, - right: inset.right - ) - - return bounds.inset(by: editingInsets) + return bounds.inset(by: inset) } } From 61880a02b5c4990b0138217716c9d75587057242 Mon Sep 17 00:00:00 2001 From: Moon <1tjen@naver.com> Date: Tue, 3 Oct 2023 22:33:29 +0900 Subject: [PATCH 42/48] =?UTF-8?q?feat:=20ToDoDetailViewModel=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ViewModel/ToDoDetailViewModel.swift | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 ProjectManager/ProjectManager/ViewModel/ToDoDetailViewModel.swift diff --git a/ProjectManager/ProjectManager/ViewModel/ToDoDetailViewModel.swift b/ProjectManager/ProjectManager/ViewModel/ToDoDetailViewModel.swift new file mode 100644 index 000000000..646d1a75d --- /dev/null +++ b/ProjectManager/ProjectManager/ViewModel/ToDoDetailViewModel.swift @@ -0,0 +1,82 @@ +// +// ToDoDetailViewModel.swift +// ProjectManager +// +// Created by Moon on 2023/10/02. +// + +import UIKit +import Combine + +final class ToDoDetailViewModel { + private let todo: ToDo + private let dataManager: DataManager + + // 뷰에 데이터 보내는 용(뷰의 상태) + let todoSubject: CurrentValueSubject + + @Published var isEnableEdit: Bool + @Published var background: UIColor? + + // 뷰에서 데이터 받는 용(뷰의 인풋) + var title: String? + var deadline: Date = .init() + var body: String? + + // ToDo를 새로 만드는 경우 + init(dataManager: DataManager) { + self.dataManager = dataManager + self.todo = dataManager.createToDo(category: .todo) + self.todoSubject = .init(todo) + self.isEnableEdit = true + self.background = .systemBackground + + setUpNotifications() + } + + // 셀을 선택해서 ToDo를 받는 경우 + init(todo: ToDo, dataManager: DataManager) { + self.todo = todo + self.dataManager = dataManager + self.todoSubject = .init(todo) + self.isEnableEdit = false + self.background = .systemGray6 + + setUpNotifications() + } + + private func setUpNotifications() { + // 유저와 상호작용이 가능하도록 하고 배경색 변경 + NotificationCenter.default + .addObserver(self, + selector: #selector(enableEditContent), + name: NSNotification.Name("editButtonAction"), + object: nil + ) + // viewWillDisappear 시점을 받아 dataManager에서 저장 수행 + NotificationCenter.default + .addObserver( + self, + selector: #selector(saveTodo), + name: NSNotification.Name("ToDoDetailViewWillDisappear"), + object: nil + ) + } + + // 유저와 상호작용이 가능하도록 하고 배경색 변경 + @objc + private func enableEditContent() { + self.isEnableEdit = true + self.background = .systemBackground + } + + // viewWillDisappear 시점을 받아 dataManager에서 저장 수행 + @objc + private func saveTodo() { + todo.title = self.title + todo.deadline = self.deadline + todo.body = self.body + + dataManager.saveContext() + } +} From 05cc62818b808eecd6d910fbaf77fcc4605a993f Mon Sep 17 00:00:00 2001 From: Moon <1tjen@naver.com> Date: Tue, 3 Oct 2023 22:34:53 +0900 Subject: [PATCH 43/48] =?UTF-8?q?feat:=20=EC=A0=80=EC=9E=A5=EC=9D=84=20?= =?UTF-8?q?=EC=88=98=ED=96=89=ED=95=A0=20=EB=95=8C=20=EB=AA=A8=EB=8D=B8?= =?UTF-8?q?=EC=9D=B4=20=EB=B3=80=EA=B2=BD=EB=90=A8=EC=9D=84=20=EC=95=8C?= =?UTF-8?q?=EB=A6=AC=EB=8A=94=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ProjectManager/Model/ToDo/DataManager.swift | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/ProjectManager/ProjectManager/Model/ToDo/DataManager.swift b/ProjectManager/ProjectManager/Model/ToDo/DataManager.swift index 0c2d85257..4d8f70245 100644 --- a/ProjectManager/ProjectManager/Model/ToDo/DataManager.swift +++ b/ProjectManager/ProjectManager/Model/ToDo/DataManager.swift @@ -55,6 +55,7 @@ final class DataManager { do { try viewContext.save() + postCalledSaveContext() } catch let error as NSError { print("Unresolved error: \(error), \(error.userInfo)") } @@ -63,4 +64,12 @@ final class DataManager { func deleteItem(_ todo: ToDo) { viewContext.delete(todo) } + + private func postCalledSaveContext() { + NotificationCenter.default + .post( + name: NSNotification.Name("CalledSaveContext"), + object: nil + ) + } } From 49e1b6a624ad8a539a862fbcd5e80b1a286d62ea Mon Sep 17 00:00:00 2001 From: Moon <1tjen@naver.com> Date: Tue, 3 Oct 2023 22:48:40 +0900 Subject: [PATCH 44/48] =?UTF-8?q?refactor:=20ToDoDetailViewController?= =?UTF-8?q?=EC=9D=98=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EB=B0=94=EC=9D=B8?= =?UTF-8?q?=EB=94=A9=EC=9D=84=20Combine=EC=9C=BC=EB=A1=9C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ListViewController - ToDoDetailViewController로 전환 시 ToDoDetailViewModel을 이용하도록 수정 ListViewModel - ToDoDetailViewController의 Done 시점을 받아 dataManager에서 저장 수행하던 기능 제거 - DataManager의 저장 시점을 받아 데이터를 다시 로드할 수 있도록 기능 추가 --- .../View/ListViewController.swift | 10 +- .../View/ToDoDetailViewController.swift | 118 +++++++++--------- .../ViewModel/ListViewModel.swift | 16 +-- 3 files changed, 73 insertions(+), 71 deletions(-) diff --git a/ProjectManager/ProjectManager/View/ListViewController.swift b/ProjectManager/ProjectManager/View/ListViewController.swift index 3b2663d3a..6cbdecdb0 100644 --- a/ProjectManager/ProjectManager/View/ListViewController.swift +++ b/ProjectManager/ProjectManager/View/ListViewController.swift @@ -46,8 +46,8 @@ final class ListViewController: UIViewController { @objc private func addTodo() { - let todo = listViewModel.dataManager.createToDo(category: .todo) - let detailViewController = ToDoDetailViewController(isNew: true, todo: todo) + let viewModel = ToDoDetailViewModel(dataManager: listViewModel.dataManager) + let detailViewController = ToDoDetailViewController(viewModel: viewModel) present(detailViewController, animated: true) } @@ -133,7 +133,11 @@ extension ListViewController: UITableViewDelegate { return } - let detailViewController = ToDoDetailViewController(isNew: false, todo: todo) + let viewModel = ToDoDetailViewModel( + todo: todo, + dataManager: listViewModel.dataManager + ) + let detailViewController = ToDoDetailViewController(viewModel: viewModel) present(detailViewController, animated: true) } diff --git a/ProjectManager/ProjectManager/View/ToDoDetailViewController.swift b/ProjectManager/ProjectManager/View/ToDoDetailViewController.swift index 4b84fa68f..bbf9555d7 100644 --- a/ProjectManager/ProjectManager/View/ToDoDetailViewController.swift +++ b/ProjectManager/ProjectManager/View/ToDoDetailViewController.swift @@ -6,6 +6,7 @@ // import UIKit +import Combine final class ToDoDetailViewController: UIViewController { // MARK: - Title Bar property @@ -34,7 +35,7 @@ final class ToDoDetailViewController: UIViewController { button.setTitleColor(.systemBlue, for: .normal) button.titleLabel?.adjustsFontForContentSizeCategory = true button.titleLabel?.font = .preferredFont(forTextStyle: .title3) - button.addAction(doneAction(), for: .touchUpInside)// todo를 업데이트하고 뷰모델에 알림 전송 후 dismiss + button.addAction(dismissAction(), for: .touchUpInside) return button }() @@ -106,19 +107,18 @@ final class ToDoDetailViewController: UIViewController { return view }() - private let todo: ToDo + private let viewModel: ToDoDetailViewModel + private var cancellables = Set() - init(isNew: Bool, todo: ToDo) { - self.todo = todo + init(viewModel: ToDoDetailViewModel) { + self.viewModel = viewModel super.init(nibName: nil, bundle: nil) configureUI() setUpDelegates() - setUpLeftButton(isNew: isNew) - setUpEditableContent(isNew: isNew) - setUpToDoDetailView(todo) - setUpTitle(todo: todo) + setUpLeftButton(isNew: viewModel.isEnableEdit) + setUpBindings() } required init?(coder: NSCoder) { @@ -128,7 +128,7 @@ final class ToDoDetailViewController: UIViewController { override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) - doneToDoDetail() + postToDoDetailViewWillDisappear() } private func setUpDelegates() { @@ -139,74 +139,40 @@ final class ToDoDetailViewController: UIViewController { private func setUpLeftButton(isNew: Bool) { if isNew { leftButton.setTitle("Cancel", for: .normal) - leftButton.addAction(dismissAction(), for: .touchUpInside)// 저장 없이 dismiss만 수행 + leftButton.addAction(dismissAction(), for: .touchUpInside) // TODO: 저장 없이 닫을 수 있도록 기능 추가 } else { leftButton.setTitle("Edit", for: .normal) leftButton.addAction(enableEditContentAction(), for: .touchUpInside)// 유저와 상호작용이 가능하도록 하고 배경색 변경 } } - - // todo를 업데이트하고 뷰모델에 알림 전송 후 dismiss - private func doneAction() -> UIAction { - return UIAction { [weak self] _ in - self?.doneToDoDetail() - self?.dismiss(animated: true) - } - } - // 저장 없이 dismiss만 수행 + private func dismissAction() -> UIAction { return UIAction { [weak self] _ in self?.dismiss(animated: true) } } - // 유저와 상호작용이 가능하도록 하고 배경색 변경 + // 유저와 상호작용이 가능하도록 하고 배경색 변경할 수 있도록 뷰 모델에 알림 private func enableEditContentAction() -> UIAction { return UIAction { [weak self] _ in - [self?.titleTextField, self?.datePicker, self?.bodyTextView].forEach { - $0?.isUserInteractionEnabled = true - $0?.backgroundColor = .systemBackground - } - } - } - - // 기존의 todo일 경우 유저와 상호작용 불가능하고 배경색은 회색 - private func setUpEditableContent(isNew: Bool) { - if isNew == false { - [titleTextField, datePicker, bodyTextView].forEach { - $0.isUserInteractionEnabled = false - $0.backgroundColor = .systemGray6 - } + self?.postEditButtonAction() } } - private func setUpToDoDetailView(_ todo: ToDo) { - titleTextField.text = todo.title - datePicker.date = todo.deadline ?? Date() - bodyTextView.text = todo.body - } - - private func setUpTitle(todo: ToDo) { - titleLabel.text = todo.category - } - - private func updateToDo() { - todo.title = titleTextField.text - todo.deadline = datePicker.date - todo.body = bodyTextView.text - } - - private func postToDoDetailDone() { + private func postEditButtonAction() { NotificationCenter.default .post( - name: NSNotification.Name("ToDoDetailDone"), + name: NSNotification.Name("editButtonAction"), object: nil ) } - - private func doneToDoDetail() { - updateToDo() - postToDoDetailDone() + + private func postToDoDetailViewWillDisappear() { + NotificationCenter.default + .post( + name: NSNotification.Name("ToDoDetailViewWillDisappear"), + object: nil + ) } } @@ -301,3 +267,43 @@ extension ToDoDetailViewController: UITextViewDelegate { return newLength <= 1000 } } + +// MARK: - Combine +extension ToDoDetailViewController { + private func setUpBindings() { + bindViewModelToView() + bindViewToViewModel() + } + + private func bindViewToViewModel() { + titleTextField.publisher(for: \.text) + .assign(to: \.title, on: viewModel) + .store(in: &cancellables) + datePicker.publisher(for: \.date) + .assign(to: \.deadline, on: viewModel) + .store(in: &cancellables) + bodyTextView.publisher(for: \.text) + .assign(to: \.body, on: viewModel) + .store(in: &cancellables) + } + + private func bindViewModelToView() { + viewModel.todoSubject + .sink(receiveValue: { [weak self] in + self?.titleLabel.text = $0.category + self?.titleTextField.text = $0.title + self?.datePicker.date = $0.deadline ?? Date() + self?.bodyTextView.text = $0.body + }) + .store(in: &cancellables) + + [titleTextField, datePicker, bodyTextView].forEach { + viewModel.$isEnableEdit + .assign(to: \.isUserInteractionEnabled, on: $0) + .store(in: &cancellables) + viewModel.$background + .assign(to: \.backgroundColor, on: $0) + .store(in: &cancellables) + } + } +} diff --git a/ProjectManager/ProjectManager/ViewModel/ListViewModel.swift b/ProjectManager/ProjectManager/ViewModel/ListViewModel.swift index 4fdcad6b6..f3703ed45 100644 --- a/ProjectManager/ProjectManager/ViewModel/ListViewModel.swift +++ b/ProjectManager/ProjectManager/ViewModel/ListViewModel.swift @@ -38,9 +38,8 @@ final class ListViewModel { setUpNotifications() } - // ListViewController의 viewDidLoad 시점을 받아 모델을 로드할 수 있도록 함 - // ToDoDetailViewController의 Done 시점을 받아 dataManager에서 저장 수행 private func setUpNotifications() { + // viewDidLoad 시점을 받아 모델을 로드할 수 있도록 함 NotificationCenter.default .addObserver( self, @@ -48,28 +47,21 @@ final class ListViewModel { name: NSNotification.Name("ListViewControllerViewDidLoad"), object: nil ) + // dataManager에서 저장이 이루어졌을 때 모델을 다시 로드 NotificationCenter.default .addObserver( self, - selector: #selector(saveTodo), - name: NSNotification.Name("ToDoDetailDone"), + selector: #selector(loadTodoList), + name: NSNotification.Name("CalledSaveContext"), object: nil ) } - // ListViewController의 viewDidLoad 시점을 받아 모델을 로드할 수 있도록 함 @objc private func loadTodoList() { todoList = dataManager.fetchToDoList() } - // ToDoDetailViewController의 Done 시점을 받아 dataManager에서 저장 수행 - @objc - private func saveTodo() { - dataManager.saveContext() - loadTodoList() - } - func bindCount(_ handler: @escaping (ListViewModel) -> Void) { handler(self) bindCount = handler From 505db351cc4e5accbad8d273c1853767d66b1436 Mon Sep 17 00:00:00 2001 From: Moon <1tjen@naver.com> Date: Wed, 4 Oct 2023 01:42:32 +0900 Subject: [PATCH 45/48] =?UTF-8?q?feat:=20=EC=8A=A4=EC=99=80=EC=9D=B4?= =?UTF-8?q?=ED=94=84=ED=95=98=EC=97=AC=20=EC=82=AD=EC=A0=9C=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../View/ListViewController.swift | 13 ++++++++++++ .../ViewModel/ListViewModel.swift | 21 +++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/ProjectManager/ProjectManager/View/ListViewController.swift b/ProjectManager/ProjectManager/View/ListViewController.swift index 6cbdecdb0..da3fb58de 100644 --- a/ProjectManager/ProjectManager/View/ListViewController.swift +++ b/ProjectManager/ProjectManager/View/ListViewController.swift @@ -141,4 +141,17 @@ extension ListViewController: UITableViewDelegate { present(detailViewController, animated: true) } + + func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { + let deleteAction = UIContextualAction(style: .destructive, title: "Delete") { _, _, _ in + NotificationCenter.default + .post( + name: NSNotification.Name("SwipeDelete"), + object: nil, + userInfo: ["index" : indexPath.row] + ) + } + + return UISwipeActionsConfiguration(actions: [deleteAction]) + } } diff --git a/ProjectManager/ProjectManager/ViewModel/ListViewModel.swift b/ProjectManager/ProjectManager/ViewModel/ListViewModel.swift index f3703ed45..0e3977924 100644 --- a/ProjectManager/ProjectManager/ViewModel/ListViewModel.swift +++ b/ProjectManager/ProjectManager/ViewModel/ListViewModel.swift @@ -55,6 +55,14 @@ final class ListViewModel { name: NSNotification.Name("CalledSaveContext"), object: nil ) + // 스와이프로 삭제 시 index를 받아 해당 ToDo를 삭제 + NotificationCenter.default + .addObserver( + self, + selector: #selector(deleteToDo), + name: NSNotification.Name("SwipeDelete"), + object: nil + ) } @objc @@ -62,6 +70,19 @@ final class ListViewModel { todoList = dataManager.fetchToDoList() } + @objc + private func deleteToDo(_ notification: Notification) { + guard let userInfo = notification.userInfo, + let index = userInfo["index"] as? Int, + let todo = todoList?[safe: index] + else { + return + } + + dataManager.deleteItem(todo) + loadTodoList() + } + func bindCount(_ handler: @escaping (ListViewModel) -> Void) { handler(self) bindCount = handler From 883741b68e67f9ca9185620fcfd5c360a560fbba Mon Sep 17 00:00:00 2001 From: Moon <1tjen@naver.com> Date: Wed, 4 Oct 2023 15:51:03 +0900 Subject: [PATCH 46/48] =?UTF-8?q?feat:=20UITextView=20extension=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EB=B0=94=EC=9D=B8=EB=94=A9=EC=9D=84=20=EC=9C=84?= =?UTF-8?q?=ED=95=9C=20textPublisher=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ProjectManager.xcodeproj/project.pbxproj | 12 ++++++++++++ .../View/Extension/UITextView+.swift | 19 +++++++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 ProjectManager/ProjectManager/View/Extension/UITextView+.swift diff --git a/ProjectManager/ProjectManager.xcodeproj/project.pbxproj b/ProjectManager/ProjectManager.xcodeproj/project.pbxproj index 1abc48d8c..541d85949 100644 --- a/ProjectManager/ProjectManager.xcodeproj/project.pbxproj +++ b/ProjectManager/ProjectManager.xcodeproj/project.pbxproj @@ -22,6 +22,7 @@ 28DBCB962AC9292C00DFC58E /* InsetTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28DBCB952AC9292C00DFC58E /* InsetTextField.swift */; }; 28DBCB982AC94A5F00DFC58E /* UIFont+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28DBCB972AC94A5F00DFC58E /* UIFont+.swift */; }; 28DBCB9A2ACAD19500DFC58E /* ToDoDetailViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28DBCB992ACAD19500DFC58E /* ToDoDetailViewModel.swift */; }; + 28DBCBA22ACD40E600DFC58E /* UITextView+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28DBCBA12ACD40E600DFC58E /* UITextView+.swift */; }; C7431F0625F51E1D0094C4CF /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7431F0525F51E1D0094C4CF /* AppDelegate.swift */; }; C7431F0825F51E1D0094C4CF /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7431F0725F51E1D0094C4CF /* SceneDelegate.swift */; }; C7431F0A25F51E1D0094C4CF /* ListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7431F0925F51E1D0094C4CF /* ListViewController.swift */; }; @@ -45,6 +46,7 @@ 28DBCB952AC9292C00DFC58E /* InsetTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InsetTextField.swift; sourceTree = ""; }; 28DBCB972AC94A5F00DFC58E /* UIFont+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIFont+.swift"; sourceTree = ""; }; 28DBCB992ACAD19500DFC58E /* ToDoDetailViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToDoDetailViewModel.swift; sourceTree = ""; }; + 28DBCBA12ACD40E600DFC58E /* UITextView+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UITextView+.swift"; sourceTree = ""; }; C7431F0225F51E1D0094C4CF /* ProjectManager.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ProjectManager.app; sourceTree = BUILT_PRODUCTS_DIR; }; C7431F0525F51E1D0094C4CF /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; C7431F0725F51E1D0094C4CF /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; @@ -98,6 +100,7 @@ 2897D3762AC05F8D00662BAA /* View */ = { isa = PBXGroup; children = ( + 28DBCBA02ACD40DC00DFC58E /* Extension */, C7431F0925F51E1D0094C4CF /* ListViewController.swift */, 28CA66E62AC0FE3100FFD7A9 /* ListHeader.swift */, 28CA66E42AC0F72600FFD7A9 /* ListCell.swift */, @@ -143,6 +146,14 @@ path = Utility; sourceTree = ""; }; + 28DBCBA02ACD40DC00DFC58E /* Extension */ = { + isa = PBXGroup; + children = ( + 28DBCBA12ACD40E600DFC58E /* UITextView+.swift */, + ); + path = Extension; + sourceTree = ""; + }; C7431EF925F51E1D0094C4CF = { isa = PBXGroup; children = ( @@ -251,6 +262,7 @@ 28CA66F22AC1A08600FFD7A9 /* Reusable.swift in Sources */, C7431F0A25F51E1D0094C4CF /* ListViewController.swift in Sources */, 28CA66EB2AC1805300FFD7A9 /* ListViewModel.swift in Sources */, + 28DBCBA22ACD40E600DFC58E /* UITextView+.swift in Sources */, 28DBCB9A2ACAD19500DFC58E /* ToDoDetailViewModel.swift in Sources */, 28DBCB982AC94A5F00DFC58E /* UIFont+.swift in Sources */, C7431F0625F51E1D0094C4CF /* AppDelegate.swift in Sources */, diff --git a/ProjectManager/ProjectManager/View/Extension/UITextView+.swift b/ProjectManager/ProjectManager/View/Extension/UITextView+.swift new file mode 100644 index 000000000..2b386e028 --- /dev/null +++ b/ProjectManager/ProjectManager/View/Extension/UITextView+.swift @@ -0,0 +1,19 @@ +// +// UITextView+.swift +// ProjectManager +// +// Created by Moon on 2023/10/04. +// + +import UIKit +import Combine + +extension UITextView { + var textPublisher: AnyPublisher { + NotificationCenter.default + .publisher(for: UITextView.textDidChangeNotification, object: self) + .compactMap { $0.object as? UITextView } // 위 publisher의 리턴이 Notification이기 때문에 전달한 object(Any 타입)를 UITextView로 캐스팅 + .compactMap(\.text) // 위에서 캐스팅한 텍스트 뷰에서 text 추출 + .eraseToAnyPublisher() // 위에서 받은 text를 Publisher로 감쌈 + } +} From 16ad845c0e2c588c02221b575956d66161cc445a Mon Sep 17 00:00:00 2001 From: Moon <1tjen@naver.com> Date: Wed, 4 Oct 2023 15:52:48 +0900 Subject: [PATCH 47/48] =?UTF-8?q?fix:=20datePicker=EC=99=80=20bodyTextView?= =?UTF-8?q?=EC=9D=98=20=EB=B0=94=EC=9D=B8=EB=94=A9=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 최초 실행 시 한 번만 값을 보냈기 때문에 값이 변경 될 때마다 값을 보낼 수 있도록 수정 --- .../View/ToDoDetailViewController.swift | 23 ++++++++++++++++--- .../ViewModel/ToDoDetailViewModel.swift | 2 +- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/ProjectManager/ProjectManager/View/ToDoDetailViewController.swift b/ProjectManager/ProjectManager/View/ToDoDetailViewController.swift index bbf9555d7..0a6e74235 100644 --- a/ProjectManager/ProjectManager/View/ToDoDetailViewController.swift +++ b/ProjectManager/ProjectManager/View/ToDoDetailViewController.swift @@ -119,6 +119,7 @@ final class ToDoDetailViewController: UIViewController { setUpDelegates() setUpLeftButton(isNew: viewModel.isEnableEdit) setUpBindings() + setUpDatePickerBinding() } required init?(coder: NSCoder) { @@ -145,6 +146,15 @@ final class ToDoDetailViewController: UIViewController { leftButton.addAction(enableEditContentAction(), for: .touchUpInside)// 유저와 상호작용이 가능하도록 하고 배경색 변경 } } + + // datePicker의 값이 변경될 때마다 뷰 모델에 데이터를 보낼 수 있도록 타겟 추가 + private func setUpDatePickerBinding() { + datePicker.addTarget( + self, + action: #selector(bindDatePicker), + for: .valueChanged + ) + } private func dismissAction() -> UIAction { return UIAction { [weak self] _ in @@ -279,12 +289,19 @@ extension ToDoDetailViewController { titleTextField.publisher(for: \.text) .assign(to: \.title, on: viewModel) .store(in: &cancellables) + bindDatePicker() + bodyTextView.textPublisher + .assign(to: \.body, on: viewModel) + .store(in: &cancellables) + } + + // 최초 실행 시 한 번만 값을 보내기 때문에 값이 변경 될 때마다 값을 보낼 수 있도록 + // datePicker.addTarget의 selector로 사용 + @objc + private func bindDatePicker() { datePicker.publisher(for: \.date) .assign(to: \.deadline, on: viewModel) .store(in: &cancellables) - bodyTextView.publisher(for: \.text) - .assign(to: \.body, on: viewModel) - .store(in: &cancellables) } private func bindViewModelToView() { diff --git a/ProjectManager/ProjectManager/ViewModel/ToDoDetailViewModel.swift b/ProjectManager/ProjectManager/ViewModel/ToDoDetailViewModel.swift index 646d1a75d..a72680d15 100644 --- a/ProjectManager/ProjectManager/ViewModel/ToDoDetailViewModel.swift +++ b/ProjectManager/ProjectManager/ViewModel/ToDoDetailViewModel.swift @@ -21,7 +21,7 @@ final class ToDoDetailViewModel { // 뷰에서 데이터 받는 용(뷰의 인풋) var title: String? var deadline: Date = .init() - var body: String? + var body: String = .init() // ToDo를 새로 만드는 경우 init(dataManager: DataManager) { From aba499489ccabb8ea9a22974b79dfc96b62e81d0 Mon Sep 17 00:00:00 2001 From: Moon <1tjen@naver.com> Date: Wed, 4 Oct 2023 18:32:01 +0900 Subject: [PATCH 48/48] =?UTF-8?q?chore:=20=EC=A3=BC=EC=84=9D=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ProjectManager/View/ToDoDetailViewController.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ProjectManager/ProjectManager/View/ToDoDetailViewController.swift b/ProjectManager/ProjectManager/View/ToDoDetailViewController.swift index 0a6e74235..2e7332d7c 100644 --- a/ProjectManager/ProjectManager/View/ToDoDetailViewController.swift +++ b/ProjectManager/ProjectManager/View/ToDoDetailViewController.swift @@ -285,6 +285,7 @@ extension ToDoDetailViewController { bindViewToViewModel() } + // 뷰에서 입력 받은 데이터를 뷰 모델로 보냄 private func bindViewToViewModel() { titleTextField.publisher(for: \.text) .assign(to: \.title, on: viewModel) @@ -304,6 +305,7 @@ extension ToDoDetailViewController { .store(in: &cancellables) } + // 뷰 모델의 데이터를 받아 뷰에 적용 private func bindViewModelToView() { viewModel.todoSubject .sink(receiveValue: { [weak self] in