iOS 13강 Todo list
table view → static cells (우리가 설정한 그대로 보여주겠다)
style 설정할 수 있음 (groupped, inset groupped (inset:마진이 있다))
- collection view 완성하기
- collection view header,custom cell로 구성됨
- header도 custom이고 class에 TodoListHeaderView class가 적용돼있음
- cell에도 TodoListCell 상속
- Cell에서 중요한 것: resuable identifier, header도 해야함
- 다양한 컬럼에 여러가지 아이템을 넣을 수 있음 → 레이아웃의 사이즈 등을 정해줘야함: UICollectionViewFlowLayout 프로토콜 설정해줘야함
- cell 사이즈 계산해주기
테이블 뷰처럼 row 하나로 보여주려고 함
//UICollectionViewDelegateFlowLayout 프로토콜
let width = collectionView.bounds.width
let height: CGFloat = 50
return CGSize(width: width, height: height)
- UI 컴포넌트는 연결돼있는 상태, check랑 delete 버튼이 눌리면 투두리스트를 관리하는 객체한테 눌렸다고 알려줄 수 있게 알려줘야함: doneButtonTapHandler, deleteButtonTapHandler 클로저 실행 → 투두리스트 객체가 관리할 수 있게 외부에서 코드 넣어줄 예정. 클로저의 구현은 밖에서 해준다는 뜻(TodoListCell안에서 말고)
Todo 객체 알아보기 (struct Todo)
struct Todo: Codable, Equatable {
//연관성 있는 정보들
1. **id(구분자)**
2. 완료됐는지 안됐는지 :isDone
3. 실제 내용에 대해: detail
4. 오늘 할 일 인지?: isToday
//method 두개
1. update (스스로 프로퍼티의 값을 바꾸는)
2. update 시킬 때 여러개의 투두 중 누구를 업데이트 시킬거냐를 정할 때 구분자를 비교해서 동일한 객체를 업데이트 시켜야함. 동등비교를 위해
} -> Equatable 프로토콜 준수했기 때문에 내가 봤을 때 static func == (lhs:, rhs:) 이 틀을 준수했다는 의미인거같음
- 동등비교 method 정의
//왼쪽의 아이디와 오른쪽 아이디가 같으면 동일하다고 봐라
return lhs.id == rhs.id
- update method 완성
self.isDone(기존의 값) = isDone(파라미터로 들어온거)
self.detail = detail
self.isToday = isToday
Todo manager
viewmodel이 자기가 모르는 부분에 대해서는 todo manager에게 물어보도록 설정함
- 싱글톤 객체에 대해서 더 찾아봐야함 (static let shared = TodoManager())
- static var lastId: Int = 0 (새로운 투두 추가할 때 이거에 기반해서 새로운 아이디 생성할 예정)
- Todo 생성하는 로직 추가
//투두를 생성하기 위해서는 다음 아이디를 알아야함
let nextId = TodoManager.lastId + 1 //새로운 아이디 생성
TodoManager.lastId = nextId //라스트아이디 갱신
return Todo(id: nextId, isDone: false, detail:detail, isToday:isToday)
- Todo 배열에 추가하는 로직 추가
todos.append(todo)
//여기서 어플 뻗으면 디스크에 저장x -> 그때그때 저장해줘야함
saveTodo() //디스크에 투두 바로 쓰기
- Todo 지우는 로직 추가 + 바로 싱크
//todos.delete, todos.remove 가능
//동일한 인덱스 찾아서 지우기 가능
if let index = todos.firstIndex(of: todo){
todos.remove(at: index)
}
saveTodo()
//들어온 todo를 찾아서 지우기
//알고리즘적으로는 이게 더 효율적일수있음
-------
todos = todos.filter { existingTodo in
return existingTodo.id != todo.id
}
//있는 todo들의 아이디랑 지우라고 넘겨받은 todo의 아이디랑 비교해서
//동일하지 않은 것들, 즉 지우지 않아야하는 것들만 골라서 return
todos = todos.filter { $0.id != todo.id }
- Todo 업데이트하는 로직 추가 + 바로 싱크
//해당하는 투두를 찾아서 넘겨준 내용으로 업데이트하면됨
guard let index = todos.firstIndex(of: todo) else {return}
todos[index].update(isDone: todo.isDone, detail: todo.detail, isToday: todo.isToday)
saveTodo()
Todo ViewModel
todo manager는 결국 todo viewmodel이 사용하는 것임
- TodoViewController에 TodoViewModel 만들기
let todoListViewModel = TodoViewModel()
- TodoListViewModel이 해야할 일
- 데이터 불러오기: 디스크에 있는 투두 목록들(viewDidLoad()할때)
todoListViewModel.loadTasks()
//manager.retrieveTodos() : 매니저한테 시키는거임 결국
- 섹션이 몇갠지
return todoListViewModel.numofSection
- 섹션별 아이템이 몇갠지
//section이 두개임(0,1)
//section=0 -> today, section=1 -> upcomming
if section == 0 {
return todoListViewModel.todayTodos.count
} else {
return todoListViewModel.upcommingTodos.count
}
- 셀 어떻게 구성할건지(프로토콜), 셀 표현할 때 필요한 todo
//section이 어딘지에 따라 어디서 정보를 가져와야할지 달라짐
var todo: Todo
if indexPath.section == 0 {
todo = todoListviewModel.todayTodos[indexPath.item]
} else {
todo = todoListviewModel.upcommingTodos[indexPath.item]
}
cell.updateUI(todo: todo)
→ 아직 TodoListCell에서 업데이트하는 로직 작성안해서 데이터가 제대로 보이지않고 있음
TodoListCell
: 뷰 객체이기 때문에 최대한 모델이나 로직에 직접적으로 접근하지 않기 위해서 클로져를 만들어놓고 외부에서 코드를 가져와서 꽂는 식으로 사용하려고 함
→ 정보 보여주기 (업데이트)
→ 체크 버튼 눌렀을 때 설정
→ 삭제 버튼 눌렀을 때 설정
- 셀 업데이트
func updateUI(todo: Todo){
checkButton.isSelected = todo.isDone
descriptionLabel.text = todo.detail
descriptionLabel.alpha = todo.isDone ? 0.2 : 1 //투명도
deleteButton.isHidden = todo.isDone == false //delete버튼 숨기는 조건은 todo가 아직 done되지않았을때
showStrikeThrough(todo.isDone)
}
- reset 로직 구현 → 셀이 재사용되기 떄문에 처음 상태로 돌려놔야함 그 기능을 구현하는 것.
reset 언제 해~ 스토리보드에서 깨어났을 때, 화면 밖으로 나가서 재사용될 떄
func reset(){
descriptionLabel.alpha = 1
deleteButton.isHidden = true
showStrikeThrough(false)
}
-
버튼 디자인(default, selected)
-
checkButtonTapped 체크버튼 처리 (체크버튼이 눌렸을 때 → 토글되면 됨)
checkButton.isSelected = !checkButton.isSelected
let isDone = checkButton.isSelected
showStrikeThrough(isDone)
descriptionLabel.alpha = isDone ? 0.2 : 1
deleteButton.isHidden = !isDone
//여기까지는 UI업데이트, 뷰 업데이트
//실제 데이터 변경은 아래 closure 사용
doneButtonTapHandler?(isDone)
- deleteButtonTapped 딜리트버튼 처리
//투두매니저가 객체 삭제하고 뷰컨트롤러가 뷰를 업데이트 한 번 하는게 좋을듯~
//그 과정은 클로져에서 로직짜놓으면 됨
deleteButtonTapHandler?()
struct를 json으로 인코딩 시켜서 디스크에 저장시키기
텍스트 인풋 뷰 추가
- 일반 view 추가 (높이 변경, 오토레이아웃 설정)
- 안에 텍스트 필드, 버튼 두개 추가 및 오토레이아웃 설정 + default, selcted 설정
- 코드랑 연결시키기(동그라미 채우기)
- 나중에 키보드 올라가면 뷰도 같이 올라갈 수 있도록 준비하기 위해서 view의 bottom constraint 영역이랑 컨트롤눌러서 코드의 inputViewBottom이랑 연결(NSLayoutConstraint)
TodoListViewController 코드 수정
- func isTodayButtonTapped랑 동그라미랑 Today 버튼 연결
- func addTaskButtonTapped 동그라미랑 플러스 버튼 연결
- 투데이버튼 토글 작업
isTodayButton.isSelected = !isTodayButton.isSelected
- addtask버튼 코드 수정 → 1) 텍스트필드안에 진짜 텍스트가 있는지 확인
guard let detail = inputTextField.text, detail.isEmpty == false else {return}
let todo = TodoManager.shared.createTodo(detail: detail, isToday: isTodayButton.isSelected)
todoListViewModel.addTodo(todo)
collectionView.reloadData()
//viewmodel에 추가해놓고 reloadData하면 뷰모델한테 다시 물어보는거기때문에 반영됨
inputTextField.text = "" //한 번 쓰이고나면 refresh 시켜주기
isTodayButton.isSelected = false
- 남은거: 투두를 더하는거, done체크하는거, delete체크하는거
todolistviewcontroller: cellforitemat 에서
cell.doneButtonTapHandler = {isDone in
todo.isDone = isDone
self.todoListViewModel.updateTodo(todo)
self.collectionView.reloadData()
}
cell.deleteButtonTapHandler = {
self.todoListViewModel.deleteTodo(todo)
self.collectionView.reloadData()
}
키보드에 따라 인풋 뷰 위치변경
- viewDidLoad에서 키보드가 올라왔는지 내려왔는지 감시
//키보드가 올라왔을 때
NotificationCenter.default.addObserver(self, selector(관찰됐을 때 어떤 메소드 호출할거냐)
: #selector(adjustInputView), name(우리가 관찰하고자하는 이벤트의 이름): UIResponder.keyboard
WillShowNotification, object:nil)
//키보드가 내려갔을 때
NotificationCenter.default.addObserver(self, selector(관찰됐을 때 어떤 메소드 호출할거냐)
: #selector(adjustInputView), name(우리가 관찰하고자하는 이벤트의 이름): UIResponder.keyboard
WillHideNotification, object:nil)
- adjustInputView 메소드 작성(인풋뷰의 높이 조절해줌)
//userInfo : dictionary 타입 -> 이 중에 알고싶은거 키보드의 높이 정보
//키보드가 다 올라와씅ㄹ때, 내려갔을때의 위치와 크기 정보를 달라
guard let keyboardFrame(위치와 사이즈까지) = userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.
cgRectValue else {return}
if noti.name == UIResponder.keyboardWillShowNotification{
let adjustmentHeight = keyboardFrame.height - view.safeAreaInsets.bottom
inputViewBottom.constant = adjustmentHeight
} else {
inputViewBottom.constant = 0
}
-
백그라운드 터치하면 내려가게 만드려고함
-
uicomponent에서 tab gesture recognizer을 전체 view와 연결
-
코드랑 연결해서 투두뷰리스트컨트롤러 코드에 ibaction 따기(tapBG)
-
키보드 내려오게 하는 코드 넣기
인풋뷰에 관심이 있으면 키보드가 뜸. 관심을 떼면 키보드가 내려감
-> 관심없다고 코드 넣으면 됨
inputTextField.resignFirstResponder()
Author And Source
이 문제에 관하여(iOS 13강 Todo list), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@hope1053/iOS-13강-Todo-list저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)