Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

프로젝트 매니저 [STEP 2-1] Moon #307

Open
wants to merge 24 commits into
base: ic_9_etialmoon
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
04eb67b
chore: MVVM 폴더 구조 생성
Sep 24, 2023
d1af0ff
refactor: ViewController에서 ListViewController로 네이밍 수정
Sep 24, 2023
4749fcc
feat: ListViewController에 tableView 추가
Sep 24, 2023
9ecf43d
chore: ListViewController 파일 이름 수정
Sep 24, 2023
55d04a8
feat: ListViewCell 생성 및 구현
Sep 24, 2023
e7f8af1
feat: ListViewHeader 생성 및 구현
Sep 25, 2023
c3c89b7
refactor: ListViewCell의 subView들을 contentView의 subView로 수정
Sep 25, 2023
d1eedda
feat: ListViewController의 TableView에 Cell 사이 간격 추가
Sep 25, 2023
6ee0f75
refactor: 뷰들의 배경색 수정
Sep 25, 2023
35a2646
refactor: ListViewCell의 deadlineLabel 네이밍 수정
Sep 25, 2023
9f35f8e
feat: Todo 모델 생성
Sep 25, 2023
2ca8a15
feat: ListViewModel 생성
Sep 25, 2023
eadf176
refactor: ListHeader, ListCell 네이밍 수정
Sep 25, 2023
0941e91
feat: identifier를 위한 Reusable 프로토콜 생성
Sep 25, 2023
d2fdb28
feat: ListCellViewModel 생성
Sep 25, 2023
5b4b6ec
refactor: ListHeader와 바인딩할 수 있도록 수정
Sep 25, 2023
5b4b5cd
feat: ListCell과 ListCellViewModel 바인딩
Sep 25, 2023
15cc06c
feat: ListHeader와 ListViewModel 바인딩
Sep 25, 2023
76e9c4f
refactor: ListViewController에서 ViewModel을 사용할 수 있도록 수정
Sep 25, 2023
ddc2e9b
feat: TodoDateFormatter, DateFormat 생성
Sep 28, 2023
0752eea
refactor: Cell에서 ViewModel을 사용하지 않도록 수정
Sep 28, 2023
588d154
refactor: todoList 모델 로드 시점 수정
Sep 28, 2023
cfbe0fd
feat: Array extension 생성 및 safe subscript 구현
Sep 28, 2023
5693a3a
refactor: 배열 접근을 safe subscript로 하도록 수정
Sep 28, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 80 additions & 8 deletions ProjectManager/ProjectManager.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,30 @@
objects = {

/* Begin PBXBuildFile section */
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 */; };
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 */; };
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 */

/* Begin PBXFileReference section */
28CA66E42AC0F72600FFD7A9 /* ListCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListCell.swift; sourceTree = "<group>"; };
28CA66E62AC0FE3100FFD7A9 /* ListHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListHeader.swift; sourceTree = "<group>"; };
28CA66EA2AC1805300FFD7A9 /* ListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListViewModel.swift; sourceTree = "<group>"; };
28CA66EC2AC1808E00FFD7A9 /* Todo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Todo.swift; sourceTree = "<group>"; };
28CA66EE2AC1865000FFD7A9 /* ListCellViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListCellViewModel.swift; sourceTree = "<group>"; };
28CA66F12AC1A08600FFD7A9 /* Reusable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Reusable.swift; sourceTree = "<group>"; };
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 = "<group>"; };
C7431F0725F51E1D0094C4CF /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
C7431F0925F51E1D0094C4CF /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; };
C7431F0925F51E1D0094C4CF /* ListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListViewController.swift; sourceTree = "<group>"; };
C7431F0E25F51E1E0094C4CF /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
C7431F1125F51E1E0094C4CF /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
C7431F1325F51E1E0094C4CF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
Expand All @@ -35,6 +47,59 @@
/* End PBXFrameworksBuildPhase section */

/* Begin PBXGroup section */
2897D3752AC05F8400662BAA /* ViewModel */ = {
isa = PBXGroup;
children = (
28CA66EA2AC1805300FFD7A9 /* ListViewModel.swift */,
28CA66EE2AC1865000FFD7A9 /* ListCellViewModel.swift */,
);
path = ViewModel;
sourceTree = "<group>";
};
2897D3762AC05F8D00662BAA /* View */ = {
isa = PBXGroup;
children = (
C7431F1025F51E1E0094C4CF /* LaunchScreen.storyboard */,
C7431F0925F51E1D0094C4CF /* ListViewController.swift */,
28CA66E62AC0FE3100FFD7A9 /* ListHeader.swift */,
28CA66E42AC0F72600FFD7A9 /* ListCell.swift */,
);
path = View;
sourceTree = "<group>";
};
2897D3772AC05F9500662BAA /* Model */ = {
isa = PBXGroup;
children = (
28CA66EC2AC1808E00FFD7A9 /* Todo.swift */,
);
path = Model;
sourceTree = "<group>";
};
2897D3782AC05F9D00662BAA /* Application */ = {
isa = PBXGroup;
children = (
C7431F0525F51E1D0094C4CF /* AppDelegate.swift */,
C7431F0725F51E1D0094C4CF /* SceneDelegate.swift */,
);
path = Application;
sourceTree = "<group>";
};
2897D3792AC05FBE00662BAA /* Resource */ = {
isa = PBXGroup;
children = (
C7431F0E25F51E1E0094C4CF /* Assets.xcassets */,
);
path = Resource;
sourceTree = "<group>";
};
28CA66F02AC1A02C00FFD7A9 /* Utility */ = {
isa = PBXGroup;
children = (
28CA66F12AC1A08600FFD7A9 /* Reusable.swift */,
);
path = Utility;
sourceTree = "<group>";
};
C7431EF925F51E1D0094C4CF = {
isa = PBXGroup;
children = (
Expand All @@ -54,11 +119,12 @@
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 */,
28CA66F02AC1A02C00FFD7A9 /* Utility */,
C7431F1325F51E1E0094C4CF /* Info.plist */,
);
path = ProjectManager;
Expand Down Expand Up @@ -133,8 +199,14 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
C7431F0A25F51E1D0094C4CF /* ViewController.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 */,
C7431F0625F51E1D0094C4CF /* AppDelegate.swift in Sources */,
28CA66E72AC0FE3100FFD7A9 /* ListHeader.swift in Sources */,
C7431F0825F51E1D0094C4CF /* SceneDelegate.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}

Expand Down
14 changes: 14 additions & 0 deletions ProjectManager/ProjectManager/Model/Todo.swift
Original file line number Diff line number Diff line change
@@ -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
Comment on lines +11 to +13
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

요 아이들을 variable로 선언하신 이유가 있을까요?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

할 일을 수정할 수 있기 때문에 계속 변경될 거라 생각해 variable로 선언했습니다!

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

사실 프로퍼티의 값이 변경될 일이 잦으면 class가 더 적합한 거 같고,
할 일을 수정하는건 할 일을 담당하는 객체가 가지고 있는 todo의 인스턴스가 변경될거 같아요

Copy link
Author

@hojun-jo hojun-jo Oct 2, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아아 변경될 일이 잦으면 class가 더 적합하겠군요
코어데이터를 사용하면 지금의 구조체를 사용하지 않고 NSManagedObject인 todo를 사용해서 값이 변경되면 뷰모델이 데이터매니저에게 저장하도록 만들 것 같습니다!

}
18 changes: 18 additions & 0 deletions ProjectManager/ProjectManager/Utility/Reusable.swift
Original file line number Diff line number Diff line change
@@ -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 { }
127 changes: 127 additions & 0 deletions ProjectManager/ProjectManager/View/ListCell.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
//
// ListCell.swift
// ProjectManager
//
// Created by Moon on 2023/09/25.
//

import UIKit

final class ListCell: UITableViewCell {
private let titleLabel: UILabel = {
let label = UILabel()
label.font = UIFont.preferredFont(forTextStyle: .title3)

return label
}()

private let descriptionLabel: UILabel = {
let label = UILabel()
label.font = UIFont.preferredFont(forTextStyle: .body)
label.textColor = .systemGray3
label.numberOfLines = 3

return label
}()

private let deadlineLabel: UILabel = {
let label = UILabel()
label.font = UIFont.preferredFont(forTextStyle: .callout)

return label
}()

private let contentStackView: UIStackView = {
let stackView = UIStackView()
stackView.axis = .vertical
stackView.alignment = .leading

return stackView
}()

// ListCell이 재사용 될 때마다 setUpViewModel을 통해 값이 들어오면 바인딩 함
private var listCellViewModel: ListCellViewModel? {
didSet {
setUpBindings()
}
}
havilog marked this conversation as resolved.
Show resolved Hide resolved

override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)

configureUI()
}

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: .zero,
bottom: .zero,
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
extension ListCell {
private func configureUI() {
addSubviews()
setUpContentStackViewConstraints()
setUpBackgroundColors()
}

private func addSubviews() {
[titleLabel, descriptionLabel, deadlineLabel].forEach {
contentStackView.addArrangedSubview($0)
}

contentView.addSubview(contentStackView)
}

private func setUpContentStackViewConstraints() {
contentStackView.translatesAutoresizingMaskIntoConstraints = false

NSLayoutConstraint.activate([
contentStackView.leadingAnchor
.constraint(equalTo: contentView.leadingAnchor, constant: 8),
contentStackView.trailingAnchor
.constraint(equalTo: contentView.trailingAnchor, constant: -8),
contentStackView.topAnchor
.constraint(equalTo: contentView.topAnchor, constant: 8),
contentStackView.bottomAnchor
.constraint(equalTo: contentView.bottomAnchor, constant: -8)
])
}

private func setUpBackgroundColors() {
backgroundColor = .systemGray6
contentView.backgroundColor = .systemBackground
}
}
Loading