-
Notifications
You must be signed in to change notification settings - Fork 147
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-2] Moon #312
base: ic_9_etialmoon
Are you sure you want to change the base?
Conversation
- todoList가 변경되면 ListHeader와 바인딩할 count가 바뀔 수 있도록 구현 - ListHeader와 바인딩할 count 변수 생성
ListViewController에서는 viewDidLoad 시점을 쏴주고, ListViewModel에서 viewDidLoad 이벤트를 받아, 데이터 로드를 시도할 수 있도록 수정
배경색이 투명이기 때문에 스크롤 시 테이블 뷰의 내용과 겹쳐 보이는 문제 해결
ListViewModel: dataManager추가, 데이터 로드, ToDoDetailDone 알림을 받아 저장 수행 ListCell: ToDo 타입을 받아 셀에 표시하도록 수정 ListViewController: 추가 버튼을 눌렀을 때 ToDo 타입도 전달하도록 수정, Cell을 선택했을 때 ToDoDetailViewController가 보이도록 기능 추가 ToDoDetailViewController: ToDo 타입을 받아 내용을 표시하는 기능 추가, Done버튼을 눌렀을 때 todo를 코어데이터의 컨텍스트에 올리고 Notification으로 뷰모델에 알리는 기능 추가
View -> Resource
폴더 구조에는 Model/ToDo 안에 있지만 프로젝트에서는 최상단에 있었기 때문에 프로젝트에서도 Model/ToDo 내부로 이동
ListViewController - ToDoDetailViewController로 전환 시 ToDoDetailViewModel을 이용하도록 수정 ListViewModel - ToDoDetailViewController의 Done 시점을 받아 dataManager에서 저장 수행하던 기능 제거 - DataManager의 저장 시점을 받아 데이터를 다시 로드할 수 있도록 기능 추가
최초 실행 시 한 번만 값을 보냈기 때문에 값이 변경 될 때마다 값을 보낼 수 있도록 수정
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
수고하셨어요 문~
return tableView | ||
}() | ||
|
||
private let listViewModel = ListViewModel() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
뷰모델은 웬만하면 외부로 부터 주입받을 수 있도록 설계하는게 좋을 것 같아요.
이유는 init시점에 다른 뷰모델을 넣어줄 수 있게 만들어 의존성을 분리해 테스트를 할 수 있게 만들 수 있기 때문이에요
마찬가지로 뷰모델이 의존하고 있는 dataManager도 외부에서 주입해주면 좋을 것 같아요.
또한 이와 관련하여 dependency injection이 무엇이고, 왜 필요한지도 공부해보시면 좋을 듯합니다.
image: UIImage(systemName: "plus"), | ||
style: .plain, | ||
target: self, | ||
action: #selector(addTodo) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
버튼의 액션은
어떤 일을 할지
가 아닌,
어떤 액션이 일어났는지
로 네이밍 해주시면 좋을 것 같아요
// viewDidLoad 시점을 뷰모델에 알려 데이터를 로드할 수 있도록 함 | ||
private func setUpViewDidLoadNotification() { | ||
NotificationCenter.default | ||
.post( | ||
name: NSNotification.Name("ListViewControllerViewDidLoad"), | ||
object: nil | ||
) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
뷰모델에 액션을 노티피케이션으로 주도록 설계하신 이유가 있을까요?
delegate, closure, notification 등 객체간의 데이터 전달 방법에 대해 각각의 활용 목적과 장단점에 대해 명확하게 정의해주시면 좋을 것 같습니다.
// MARK: - Table View Data Source | ||
extension ListViewController: UITableViewDataSource { | ||
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { | ||
return ListHeader(viewModel: listViewModel) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ListHeader는 단순히 로직 없이 화면만 표시해주는 뷰 같은데,
같은 뷰모델을 공유하면 의존성이 강결합되어 유지보수성이 떨어질 수도 있을것 같아요.
데이터를 넣어줄 때에는 최대한 raw한 데이터를 넣어주는건 어떨까요?
} | ||
|
||
private func setUpTitleBarStackViewConstraints() { | ||
titleBarStackView.translatesAutoresizingMaskIntoConstraints = false |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
요거는 view를 initialize하는 시점에 해도 괜찮을 것 같아요
init(dataManager: DataManager) { | ||
self.dataManager = dataManager | ||
self.todo = dataManager.createToDo(category: .todo) | ||
self.todoSubject = .init(todo) | ||
self.isEnableEdit = true | ||
self.background = .systemBackground | ||
|
||
setUpNotifications() | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
init시점에 너무 많은 일들을 해주고 있는 것 같아요.
isEnableEdit 같은 경우 파라미터로 주입받지 않는다면 프로퍼티 선언부에 초기값을 넣어주어도 괜찮을거 같구요
// 셀을 선택해서 ToDo를 받는 경우 | ||
init(todo: ToDo, dataManager: DataManager) { | ||
self.todo = todo | ||
self.dataManager = dataManager | ||
self.todoSubject = .init(todo) | ||
self.isEnableEdit = false | ||
self.background = .systemGray6 | ||
|
||
setUpNotifications() | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
위 코멘트가 해결된다면 이 initializer로 사라져도 될 거 같아요
@objc | ||
private func enableEditContent() { | ||
self.isEnableEdit = true | ||
self.background = .systemBackground |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
UIColor같은 경우에는 뷰모델이 아닌 뷰의 영역이라고 생각되어요.
viewmodel은 UIKit을 import하지 않고 돌아가도록 설계하면 좋을 것 같습니다.
즉 색깔의 값이 아닌 뷰의 상태를 정의하고,
뷰에서는 뷰모델에 정의된 상태를 옵저빙해서 색깔을 바꿀 수 있도록 설계해주세요
right: .zero) | ||
) | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
prepareForReuse가 빠져있는거 같네요
case todo = "yyyy. M. d." | ||
} | ||
|
||
final class TodoDateFormatter { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
요 아이는 class여야할 필요가 있을까요?
@havilog
안녕하세요 하비!
프로젝트 매니저 STEP 2-2 PR 입니다!
코어 데이터를 활용하여 CRUD를 구현하고 두 번째 화면과 뷰 모델의 바인딩은 Combine을 활용할 수 있도록 시도해 보았습니다.
이번 리뷰도 잘 부탁드립니다~😀
고민했던 점
🔍 Combine을 활용한 바인딩
assign
을 이용해 바인딩을 시도했으나 텍스트필드에 값이 반영되지 않아sink
를 이용해 확인하고자 했습니다.뷰 컨트롤러가 화면에 보이기까지
sink
에 8번 진입하며title
에 “Asdf”와 “”(빈 문자열)이 번갈아 반복되어 나오고 있었습니다.반복적으로
sink
에 진입한 이유는bindViewModelToView
와bindViewToViewModel
이 같은 프로퍼티를 사용하기 때문에 계속 순환되었던 것으로 추정하고 있습니다.따라서 뷰 모델에서 뷰에 데이터를 보내는 프로퍼티와 뷰에서 데이터를 받는 프로퍼티를 나누어 순환이 생기지 않도록 했습니다.
🔍 바인딩이 잘 이루어지지 않는 문제
위의 함수를 처음 실행했을 때는 뷰 모델에 값이 들어오는 것을 확인 했습니다. 하지만 유저가 TextField에 입력했을 때는 값이 들어오고 DatePicker를 돌리거나 TextView에 입력 했을 때는 값이 들어오지 않는 문제가 있었습니다.
따라서 DatePicker에는
addTarget
으로 값이 변경되었을 때 바인딩을 다시 수행할 수 있도록 했습니다. TextView는 extension을 통해 텍스트가 바뀌었을 때 값을 보낼 수 있도록 textPublisher를 만들었습니다.조언을 얻고 싶은 점
🔍 DataManager에서 저장을 수행할 때 Notification
코어데이터에 저장을 수행한 후 그 내용을 뷰에 업데이트 해야 하기 때문에 DataManager가 알림을 보내고 뷰 모델이 알림을 받아 데이터를 업데이트하여 뷰에 표시하고 있습니다. 현재 구조가 모델이 변경 되었을 때 뷰 모델에 알림을 보내는 부분이 맞을까요? 그렇다면 Notification 외에 이를 수행할 수 있는 방법이 있을까요?
🔍 고민했던 점의 바인딩이 잘 이루어지지 않는 문제
처음에는 아래 코드로 바인딩을 했습니다. 그런데 텍스트 필드만 바인딩이 유지 되고 데이트 픽커와 텍스트 뷰는 처음 실행할 때 한 번만 값을 받고 이후에는 값을 받지 못 했습니다. 이 원인을 알 수 있는 방법이 있을까요?
🔍
@Published
인 변수의 타입과 관련된 에러뷰 모델에서 뷰의 배경색을 업데이트하기 위해 background 변수를 가지고 있습니다. 이 변수가 옵셔널이 아닐 때에
assign
부분에서 아래와 같은 에러가 나타납니다.여기서
assign
은 UIColor(뷰 모델이 가지고 있는background
변수)를 UIColor?(titleTextField
,datePicker
,bodyTextView
의backgroundColor
)에 대입하는 것이라 생각했는데 에러 메시지를 보면 UIColor?를 UIColor로 변환할 수 없다는 내용인 것 같습니다. 이런 에러의 원인은 어떻게 찾아봐야 할까요?