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] maxhyunm #305

Open
wants to merge 20 commits into
base: ic_9_maxhyunm
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
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
225 changes: 217 additions & 8 deletions ProjectManager/ProjectManager.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1430"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "C7431F0125F51E1D0094C4CF"
BuildableName = "ProjectManager.app"
BlueprintName = "ProjectManager"
ReferencedContainer = "container:ProjectManager.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "C7431F0125F51E1D0094C4CF"
BuildableName = "ProjectManager.app"
BlueprintName = "ProjectManager"
ReferencedContainer = "container:ProjectManager.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "C7431F0125F51E1D0094C4CF"
BuildableName = "ProjectManager.app"
BlueprintName = "ProjectManager"
ReferencedContainer = "container:ProjectManager.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// ProjectManager - AppDelegate.swift
// Created by yagom.
// Copyright © yagom. All rights reserved.
//
// Last modified by Max.

import UIKit

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,24 @@
// ProjectManager - SceneDelegate.swift
// Created by yagom.
// Copyright © yagom. All rights reserved.
//
// Last modified by Max.

import UIKit

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

var window: UIWindow?


func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let windowScene = (scene as? UIWindowScene) else { return }

window = UIWindow(windowScene: windowScene)
window?.rootViewController = ViewController()
let coreDataManager = CoreDataManager()
let useCase = ToDoUseCase(dataManager: coreDataManager)
let toDoViewModel = ToDoListBaseViewModel(useCase: useCase)
let baseViewController = ToDoListBaseViewController(toDoViewModel)
let navigationViewController = UINavigationController(rootViewController: baseViewController)
window?.rootViewController = navigationViewController
window?.makeKeyAndVisible()
}

Expand Down
53 changes: 53 additions & 0 deletions ProjectManager/ProjectManager/Domain/CoreDataManager.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
//
// CoreDataManager.swift
// ProjectManager
//
// Created by Max on 2023/09/24.
//

import CoreData

struct CoreDataManager {
let persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "ToDo")
container.loadPersistentStores(completionHandler: { (_, _) in })
return container
}()

func fetchData(entityName: String, predicate: NSPredicate? = nil, sort: String? = nil) throws -> [NSManagedObject] {
let request: NSFetchRequest<NSManagedObject> = NSFetchRequest(entityName: entityName)
if let predicate {
request.predicate = predicate
}
if let sort {
let sorted = NSSortDescriptor(key: sort, ascending: true)
request.sortDescriptors = [sorted]
}
do {
let entities: [NSManagedObject] = try persistentContainer.viewContext.fetch(request)
return entities
} catch {
throw CoreDataError.dataNotFound
}
}

@discardableResult
func createData<T: NSManagedObject>(type: T.Type, values: [KeywordArgument]) throws -> T {
let newData = T(context: persistentContainer.viewContext)
return try updateData(entity: newData, values: values)
}

@discardableResult
func updateData<T: NSManagedObject>(entity: T, values: [KeywordArgument]) throws -> T {
values.forEach { entity.setValue($0.value, forKey: $0.key) }
try saveContext()
return entity
}

func deleteData<T: NSManagedObject>(entity: T) throws {
persistentContainer.viewContext.delete(entity)
try saveContext()
}

func saveContext() throws {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//
// ToDo+CoreDataClass.swift
// ProjectManager
//
// Created by Max on 2023/09/24.
//
//

import Foundation
import CoreData

@objc(ToDo)
public class ToDo: NSManagedObject {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//
// ToDo+CoreDataProperties.swift
// ProjectManager
//
// Created by Max on 2023/09/24.
//
//

import Foundation
import CoreData


extension ToDo {

@nonobjc public class func fetchRequest() -> NSFetchRequest<ToDo> {
return NSFetchRequest<ToDo>(entityName: "ToDo")
}

@NSManaged public var title: String
@NSManaged public var dueDate: Date
@NSManaged public var body: String
@NSManaged public var modifiedAt: Date
@NSManaged public var status: String

}

extension ToDo : Identifiable {
@NSManaged public var id: UUID
}
55 changes: 55 additions & 0 deletions ProjectManager/ProjectManager/Domain/ToDoUseCase.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
//
// ToDoUseCase.swift
// ProjectManager
//
// Created by Max on 2023/10/06.
//

import CoreData

struct ToDoUseCase {
private let coreDataManager: CoreDataManager

init(dataManager: CoreDataManager) {
coreDataManager = dataManager
}

func fetchDataByStatus(for status: ToDoStatus) throws -> [ToDo] {
let predicated = NSPredicate(format: "status == %@", status.rawValue)
let filtered = try coreDataManager.fetchData(entityName:"ToDo", predicate: predicated, sort: "modifiedAt")

guard let result = filtered as? [ToDo] else {
throw CoreDataError.unknown
}
return result
}

func createData(values: [KeywordArgument]) throws {
var values = values

if values.filter({ $0.key == "id" }).isEmpty {
values.append(KeywordArgument(key: "id", value: UUID()))
}

if values.filter({ $0.key == "modifiedAt" }).isEmpty {
values.append(KeywordArgument(key: "modifiedAt", value: Date()))
}

if values.filter({ $0.key == "status" }).isEmpty {
values.append(KeywordArgument(key: "status", value: ToDoStatus.toDo.rawValue))
}
try coreDataManager.createData(type: ToDo.self, values: values)
}

func updateData(_ entity: ToDo, values: [KeywordArgument]) throws {
var values = values
if values.filter({ $0.key == "modifiedAt" }).isEmpty {
values.append(KeywordArgument(key: "modifiedAt", value: Date()))
}
try coreDataManager.updateData(entity: entity, values: values)
}

func deleteData(_ entity: ToDo) throws {
try coreDataManager.deleteData(entity: entity)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
//
// ProjectManager - ToDoListViewController.swift
// Created by yagom.
// Copyright © yagom. All rights reserved.
// Last modified by Max.

import UIKit

final class ToDoListBaseViewController: UIViewController {
private let viewModel: ToDoBaseViewModelType

private let stackView: UIStackView = {
let stackView = UIStackView()
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.axis = .horizontal
stackView.distribution = .fillEqually
stackView.alignment = .top
stackView.spacing = 10
return stackView
}()

private let dateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .long
formatter.timeStyle = .none
return formatter
}()

init(_ viewModel: ToDoBaseViewModelType) {
self.viewModel = viewModel
super.init(nibName: nil, bundle: nil)
}

required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBackground
addChildren()
setupUI()
setupNavigationBar()
setupBinding()
}

private func addChildren() {
ToDoStatus.allCases.forEach { status in
let childViewModel = viewModel.inputs.addChild(status)
let childViewController = ToDoListChildViewController(status,
viewModel: childViewModel,
dateFormatter: dateFormatter)
self.addChild(childViewController)
stackView.addArrangedSubview(childViewController.view)

NSLayoutConstraint.activate([
childViewController.view.heightAnchor.constraint(equalTo: stackView.heightAnchor),
childViewController.view.centerYAnchor.constraint(equalTo: stackView.centerYAnchor)
])
}
}

private func setupUI() {
let safeArea = view.safeAreaLayoutGuide
view.backgroundColor = .systemBackground
stackView.backgroundColor = UIColor(red: 0.8, green: 0.8, blue: 0.8, alpha: 1)

view.addSubview(stackView)

NSLayoutConstraint.activate([
stackView.widthAnchor.constraint(equalTo: safeArea.widthAnchor),
stackView.heightAnchor.constraint(equalTo: safeArea.heightAnchor),
stackView.centerXAnchor.constraint(equalTo: safeArea.centerXAnchor),
stackView.centerYAnchor.constraint(equalTo: safeArea.centerYAnchor)
])
}

private func setupNavigationBar() {
self.title = "Project Manager"
let addToDo = UIAction(image: UIImage(systemName: "plus")) { [weak self] _ in
#if DEBUG
let testValue: [KeywordArgument] = [
KeywordArgument(key: "id", value: UUID()),
KeywordArgument(key: "title", value: "추가 테스트"),
KeywordArgument(key: "body", value: "테스트용입니다"),
KeywordArgument(key: "dueDate", value: Date()),
KeywordArgument(key: "modifiedAt", value: Date()),
KeywordArgument(key: "status", value: ToDoStatus.toDo.rawValue)
]
self?.viewModel.inputs.createData(values: testValue)
#endif
}
navigationItem.rightBarButtonItem = UIBarButtonItem(primaryAction: addToDo)
}

private func setupBinding() {
viewModel.outputs.error.bind { [weak self] error in
guard let self,
let error else { return }
let alertBuilder = AlertBuilder(prefferedStyle: .alert)
.setTitle(error.alertTitle)
.setMessage(error.alertMessage)
.addAction(.confirm)
.build()
present(alertBuilder, animated: true)
}
}
}

Loading