iOS 13강 Todo list

28129 단어 swiftiOSiOS

table view → static cells (우리가 설정한 그대로 보여주겠다)

style 설정할 수 있음 (groupped, inset groupped (inset:마진이 있다))

  1. collection view 완성하기
  • collection view header,custom cell로 구성됨
  • header도 custom이고 class에 TodoListHeaderView class가 적용돼있음
  • cell에도 TodoListCell 상속
  • Cell에서 중요한 것: resuable identifier, header도 해야함
  • 다양한 컬럼에 여러가지 아이템을 넣을 수 있음 → 레이아웃의 사이즈 등을 정해줘야함: UICollectionViewFlowLayout 프로토콜 설정해줘야함
  1. cell 사이즈 계산해주기
    테이블 뷰처럼 row 하나로 보여주려고 함
//UICollectionViewDelegateFlowLayout 프로토콜
let width = collectionView.bounds.width
let height: CGFloat = 50
return CGSize(width: width, height: height)
  1. 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:) 이 틀을 준수했다는 의미인거같음
  1. 동등비교 method 정의
//왼쪽의 아이디와 오른쪽 아이디가 같으면 동일하다고 봐라
return lhs.id == rhs.id
  1. 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 (새로운 투두 추가할 때 이거에 기반해서 새로운 아이디 생성할 예정)
  1. Todo 생성하는 로직 추가
//투두를 생성하기 위해서는 다음 아이디를 알아야함
let nextId = TodoManager.lastId + 1 //새로운 아이디 생성
TodoManager.lastId = nextId //라스트아이디 갱신
return Todo(id: nextId, isDone: false, detail:detail, isToday:isToday)
  1. Todo 배열에 추가하는 로직 추가
todos.append(todo)
//여기서 어플 뻗으면 디스크에 저장x -> 그때그때 저장해줘야함
saveTodo() //디스크에 투두 바로 쓰기
  1. 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 }
  1. 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이 사용하는 것임

  1. TodoViewController에 TodoViewModel 만들기
let todoListViewModel = TodoViewModel()
  1. 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

: 뷰 객체이기 때문에 최대한 모델이나 로직에 직접적으로 접근하지 않기 위해서 클로져를 만들어놓고 외부에서 코드를 가져와서 꽂는 식으로 사용하려고 함

→ 정보 보여주기 (업데이트)
→ 체크 버튼 눌렀을 때 설정
→ 삭제 버튼 눌렀을 때 설정

  1. 셀 업데이트
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)
}
  1. reset 로직 구현 → 셀이 재사용되기 떄문에 처음 상태로 돌려놔야함 그 기능을 구현하는 것.
    reset 언제 해~ 스토리보드에서 깨어났을 때, 화면 밖으로 나가서 재사용될 떄
func reset(){
	descriptionLabel.alpha = 1
	deleteButton.isHidden = true
	showStrikeThrough(false)
}
  1. 버튼 디자인(default, selected)

  2. checkButtonTapped 체크버튼 처리 (체크버튼이 눌렸을 때 → 토글되면 됨)

checkButton.isSelected = !checkButton.isSelected
let isDone = checkButton.isSelected
showStrikeThrough(isDone)
descriptionLabel.alpha = isDone ? 0.2 : 1
deleteButton.isHidden = !isDone
//여기까지는 UI업데이트, 뷰 업데이트
//실제 데이터 변경은 아래 closure 사용
doneButtonTapHandler?(isDone)
  1. deleteButtonTapped 딜리트버튼 처리
//투두매니저가 객체 삭제하고 뷰컨트롤러가 뷰를 업데이트 한 번 하는게 좋을듯~
//그 과정은 클로져에서 로직짜놓으면 됨
deleteButtonTapHandler?()

struct를 json으로 인코딩 시켜서 디스크에 저장시키기


텍스트 인풋 뷰 추가

  1. 일반 view 추가 (높이 변경, 오토레이아웃 설정)
  2. 안에 텍스트 필드, 버튼 두개 추가 및 오토레이아웃 설정 + default, selcted 설정
  3. 코드랑 연결시키기(동그라미 채우기)
  4. 나중에 키보드 올라가면 뷰도 같이 올라갈 수 있도록 준비하기 위해서 view의 bottom constraint 영역이랑 컨트롤눌러서 코드의 inputViewBottom이랑 연결(NSLayoutConstraint)

TodoListViewController 코드 수정

  1. func isTodayButtonTapped랑 동그라미랑 Today 버튼 연결
  2. func addTaskButtonTapped 동그라미랑 플러스 버튼 연결
  3. 투데이버튼 토글 작업
isTodayButton.isSelected = !isTodayButton.isSelected
  1. 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
  1. 남은거: 투두를 더하는거, 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()
}

키보드에 따라 인풋 뷰 위치변경

  1. 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)
  1. 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
}
  1. 백그라운드 터치하면 내려가게 만드려고함

  2. uicomponent에서 tab gesture recognizer을 전체 view와 연결

  3. 코드랑 연결해서 투두뷰리스트컨트롤러 코드에 ibaction 따기(tapBG)

  4. 키보드 내려오게 하는 코드 넣기

인풋뷰에 관심이 있으면 키보드가 뜸. 관심을 떼면 키보드가 내려감
-> 관심없다고 코드 넣으면 됨

inputTextField.resignFirstResponder()

좋은 웹페이지 즐겨찾기