아무튼 처음에는 섹션에 행이 하나 있을 경우 행을 삭제하면 앱이 크래시 나는 문제만 알고 고치려고 열심히 노력했는데, 나중에 알고보니 첫 번째 행을 삭제해도 앱이 꺼졌다. 프리뷰에서 왜 오류가 나는지 알려줬는데 섹션의 수가 문제였다. 섹션의 수가 변경된 것이 반영이 안 되는게 문제인 것 같았다.
분명 delete 부분에서 문제가 있는 것 같았는데 여러 방법을 시도해봤지만 잘 안 됐다. 시도한 방법을 다 지워버려서 남아있지 않다. 참고한 블로그도 임시저장 해뒀었는데 저장이 잘 안 됐는지 사라졌다🥲
문제 해결을 위해 처음에는 섹션을 지우는 부분을 추가해보기도 하고, 아래에 나와있는 방법과 같이 해당 섹션의 남은 할 일이 없으면 섹션을 삭제해보기도 했다. 근데 자꾸 앱이 꺼졌다.
이 문제의 포인트는 할 일을 삭제하고, 할 일을 다시 가져오는 것이었다.
할 일을 지웠지만 데이터를 다시 가지고 오지 않아서(업데이트가 되지 않아서), 계속 원래의 데이터를 사용하니 남은 할 일이 없으면 섹션을 삭제하는 부분이 통하지 않은 것이다. 원래의 데이터는 할 일이 잔뜩이니까요...
let remainingToDoInSection = toDo.filter { dateFormatter.string(from: $0.dueDate) == sectionDate }이 부분을 추가하니 동작이 잘 되었다.
아래는 Delete 부분의 전체 코드이다.
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MM월 dd일"
var dueDates = Array(Set(toDo.map { dateFormatter.string(from: $0.dueDate) })).sorted()
// 해당 섹션의 날짜를 가져온다.
let sectionDate = dueDates[indexPath.section]
// 해당 섹션에 대한 할 일들을 가져온다.
let toDoByDate = toDo.filter { dateFormatter.string(from: $0.dueDate) == sectionDate }
// 해당 섹션에서 삭제할 할 일을 찾는다.
let todoToDelete = toDoByDate[indexPath.row]
// 삭제된 할 일을 배열에서 제거
toDo.removeAll { $0.id == todoToDelete.id }
// 해당 섹션의 남은 할 일을 다시 가져온다.
let remainingToDoInSection = toDo.filter { dateFormatter.string(from: $0.dueDate) == sectionDate }
// 해당 섹션의 남은 할 일이 없으면 섹션을 삭제
if remainingToDoInSection.isEmpty {
// 해당 섹션의 인덱스를 찾는다.
if let sectionIndex = dueDates.firstIndex(of: sectionDate) {
// 섹션을 삭제
dueDates.remove(at: sectionIndex)
tableView.deleteSections(IndexSet(integer: sectionIndex), with: .automatic)
}
} else {
// 해당 섹션에 남은 할 일이 있으면 해당 셀만 삭제
tableView.deleteRows(at: [indexPath], with: .fade)
}
}
또 다른 문제는 alert에서 DatePicker를 보여주고 싶은데, 내가 원한 그림은 알림창이 크고, 텍스트필드가 있고, 그 아래에 DatePicker를 보여주는 것이었다.
그런데 자꾸 알림창 위에 DatePicker가 떴다. 알림창이 DatePicker보다 더 작다 보니 취소버튼도 안 눌렸다. 그래서 alertController에 frame도 지정해봤는데 원하는대로 되지 않았다.
이 블로그의 height를 지정하는 부분이 도움이 되었다.
let height: NSLayoutConstraint = NSLayoutConstraint(item: alertController.view!,
attribute: .height,
relatedBy: .equal,
toItem: nil,
attribute: .notAnAttribute,
multiplier: 1,
constant: 300)
alertController.view.addConstraint(height)
이 코드는 UIAlertController의 높이를 조정하여 DatePicker가 제대로 표시되도록 하는 데 사용됩니다.
NSLayoutConstraint생성:먼저 NSLayoutConstraint를 생성합니다. 이 때,item은alertController.view로 설정됩니다. 이는 제약 조건을 적용할 대상이 되는 뷰입니다.attribute는.height로 설정되어 뷰의 높이에 대한 제약 조건을 지정합니다.constant설정:constant속성을 사용하여 뷰의 높이를 결정합니다. 여기서는 300으로 설정되어 있으므로, UIAlertController의 높이가 300 포인트로 설정됩니다.- 제약 조건 추가: 마지막으로, 생성한 제약 조건을 UIAlertController의 뷰에 추가합니다. 이렇게 하면 UIAlertController의 높이가 300 포인트로 설정되어 DatePicker가 알맞게 표시됩니다.
이러한 조정을 통해 UIAlertController의 높이가 적절하게 설정되어 DatePicker가 텍스트 필드를 가리지 않고 제대로 표시될 수 있습니다.
이 코드의 일부는 NSLayoutConstraint를 생성하는 데 사용되는 속성과 옵션입니다. 여기서 각각의 의미를 설명하겠습니다:
relatedBy: .equal: 이 속성은item과toItem의 관계를 설명합니다..equal은item과toItem이 같은 크기를 가져야 함을 나타냅니다. 즉,alertController.view의 높이는 지정한constant값과 동일해야 합니다.toItem: nil: 이는 제약 조건을 비교할 대상을 지정하는 것입니다.nil로 설정되어 있으므로item과 비교할 다른 뷰는 없음을 나타냅니다. 따라서alertController.view의 높이를 다른 뷰와 비교하지 않고 단독으로 설정합니다.attribute: .notAnAttribute: 이는toItem이 없으므로toItem의 어떤 속성도 사용되지 않음을 나타냅니다. 따라서notAnAttribute를 사용하여toItem과 관련된 속성이 없음을 명시합니다.multiplier: 1: 이는 제약 조건에서item의 크기에 적용되는 배수를 지정합니다. 여기서는 1로 설정되어 있으므로, 다른 뷰와의 크기 비율을 조정하는 것이 아니라 고정된 값으로 설정됩니다.
이러한 설정은 UIAlertController의 높이를 지정된 값으로 고정시키기 위해 사용됩니다.
근데 이렇게 만드니까 텍스트필드 아래에 datepicker가 있는게 너무 어색했다.
그래서 텍스트필드를 누르면 datepicker가 나오게 하는 방식으로 변경했다.

이 블로그가 많은 도움이 됐다.
처음에는
textField.inputView = datePicker
이 코드를 작성해도 프리뷰에서 datepicker가 나타나지 않아서 안 되는건 줄 알았는데, 시뮬레이터에서는 잘 작동되었다. 이것도 모르고 왜 안 되지? 무한반복.. 다른 블로그 다 보고..하하..
블로그에서 설명했듯이 키보드 자리에 datepicker가 나타나는 것이라 내 프리뷰에서는 키보드가 나오지 않아서 datepicker가 나오지 않은 것이었다. 프리뷰.. 너때문이야..🥲
아무튼 결국 성공했다. 오늘 셀에 이미지 넣는 것까지 하고 싶었는데 생각보다 시간이 오래 걸린 것 같다. date를 추가하며 중복 코드가 많이 생겨서 이걸 정리하고 싶은데 적절한 방법이 무엇일지 고민이 된다.
이건 alert와 관련된 전체코드이다.
@objc func addButtonTapped() {
let alertController = UIAlertController(title: "할 일 추가", message: "날짜도 설정해주세요", preferredStyle: .alert)
alertController.view.frame = CGRect(x: 0, y: 0, width: 300, height: 500)
// DatePicker
let datePicker = UIDatePicker(frame: CGRect(x: 0, y: 0, width: 100, height: 300))
datePicker.datePickerMode = .date
datePicker.preferredDatePickerStyle = .wheels
datePicker.locale = NSLocale(localeIdentifier: "ko_KO") as Locale
datePicker.addTarget(self, action: #selector(dateChange), for: .valueChanged)
// To-do
alertController.addTextField { textField in
textField.placeholder = "할 일을 입력해주세요"
}
// Date
alertController.addTextField { textField in
textField.inputView = datePicker
textField.text = self.dateFormat(date: Date())
// ViewController class 내부에 var dateTextField = UITextField()가 선언되어 있다.
// dateFormat 함수를 사용하기 위해서이다.
self.dateTextField = textField
}
let addAction = UIAlertAction(title: "추가", style: .default) { alertAction in
let newItem = alertController.textFields?.first?.text
// 텍스트필드의 날짜를 Todo에 추가하는 부분
let selectedDate = datePicker.date
self.toDo.append(ToDo(id: (self.toDo.last?.id ?? -1) + 1, title: newItem ?? "다시 입력해주세요", isComplete: false, dueDate: selectedDate))
self.tableView.reloadData()
}
let cancelAction = UIAlertAction(title: "취소", style: .cancel)
alertController.addAction(addAction)
alertController.addAction(cancelAction)
present(alertController, animated: true)
}
func dateFormat(date: Date) -> String {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy년 MM월 dd일"
return formatter.string(from: date)
}
@objc func dateChange(_ sender: UIDatePicker) {
// 값이 변하면 UIDatePicker에서 날자를 받아와 형식을 변형해서 textField에 넣어줍니다.
self.dateTextField.text = dateFormat(date: sender.date)
}