ToDoList 앱 만들기
- 이번 주차 개인 과제는 To-Do-List를 만드는 것이다.
- 과제를 시연영상만 봤을때는 되게 간단한 앱이지 않을까 생각했지만,
TableView
가 역시나 복병이었다.
LV1
- Lv1 에서는 기본 UI를 구성해야한다.
TableView
, 추가 버튼, 데이터 구성까지 끝내야 했다.
- 우선
StoryBoard
로 프로젝트를 생성하고, 버튼과 TableView
를 Contraint
를 설정해서 화면에 위치시켰다.
- 여기서 첫번째 문제
Auto Layout
의 문제가 발생했다.
- 테이블뷰는
Leading
, Trailing
, Bottom
은 전부 Safe Area
에 맞춰서 하면되고, top
은 버튼의 Buttom
으로 하면 되서 문제가 되지 않지만, Button은 항상 오른쪽 상단에 위치해야 하는데,Leading
설정이 문제였다. Trailing
, Top
은 Safe Area
에 맞춰서 하면되는데, Leading
은 어떻게 설정을 해야할지 몰라서 일단 두고 넘어갔다.
- ToDo 구조체를 만들어서 데이터를 저장했다. Todo 구조체는
id : Int, title:String, isCompleted: Bool
의 프로퍼티를 갖고 있다. 여기서 ID는 정확히 어떤 용도로 사용되는건지 사실 잘 모르겠다.
struct Todo{
var id : Int
var title : String
var isCompleted : Bool
}
- TableView를 만들었으니, TableView Cell도 같이 만들었다. TableViewCell 에는
UILabel
과UISwitch
를 만들었다.
- TableView cell 만들때 제일 오류가 나기 쉬운건
identifier
이라서 따로 상수로 만들어서 , nib() 함수를 같이 만들어서 사용했다.
- TableView delegate 및 dataSource도 ViewController에서 설정해줬다.
- LV1 결과물은 아래와 같다.
LV2
- LV2 에서는 Todo 추가하기 기능을 구현했다.
- 추가하기 기능은
UIAlertController
를 사용했다.
IBAction
을 통해 버튼이 클릭시 UIAlertController
를 호출하는 방식으로 구현해다.
UIAlertController
사용방법은 아래와 같다.
let alertController = UIAlertController(title: "할 일 추가", message: "입력해주세요!", preferredStyle: .alert)
alertController.addTextField()
let confirm = UIAlertAction(title: "추가", style: .default){action in
if let textField = alert.textFields?.first {
self.list.append(Todo(id: 1, title: textField.text!, isCompleted: false))
self.MyTableView.reloadData()
}
}
let close = UIAlertAction(title: "닫기", style: .destructive, handler: nil)
alertController.addAction(confirm)
alertController.addAction(close)
present(alertController, animated: true, completion: nil)
- 다음은
UISwitch
를 사용해서 완료된 Todo에 취소선을 만들어줘야 한다.
- 솔직히 이게 제일 오래 걸렸다.
- 우선 취소선 자체를
Label
에 적용하는게 마냥 쉬운건 아니다. 아래의 코드를 사용해야 한다.
extension String {
func strikeThrough() -> NSAttributedString {
let attributeString: NSMutableAttributedString = NSMutableAttributedString(string: self)
attributeString.addAttribute(NSAttributedString.Key.strikethroughStyle, value: NSUnderlineStyle.single.rawValue, range: NSMakeRange(0, attributeString.length))
return attributeString
}
}
- 이제 취소선을 만들수 있는데 우리는 TableView Cell에 있는 switch로 해당 기능을 구현해야하기 때문에 Delegate를 신경써야할 수 밖에 없다.
- 만약 TableViewCell에서 IBAction을 그냥 하게 되면 제대로 작동을 하지 않을 가능성이 높다.
- 그래서 아래와 같은 방법으로 문제를 해결했다.
- Protocol 생성
- index는 indexPath 용
- TableView에서 어떤 Cell인지 확인하기 위한 용도
protocol TableViewDelegate {
func switchIsChanged(index: Int)
}
- TableViewCell에서
delegate
프로토콜 인스턴스 생성
- delegate는 TableView에서 사용하기 위함
- IBAction을 통해 Swift 값이 변하면 해당 프로토콜의 제약함수를 호출
var delagate : TableViewDelegate?
@IBAction func IsCompletedSwitchTouched(_ sender: UISwitch) {
self.delagate?.switchIsChanged(index: index)
}
- TableView Controller (MainViewController) 에서 해당 프로토콜 채택
- 여기서 실질적으로 Switch의 변화의 따른 Action을 구현
- isCompleted를 바꿔주고, tableView.ReloadData()를 통해 변한 데이터로 다시 테이블뷰를 로드 한다.
extension ViewController: UITableViewDelegate, UITableViewDataSource,TableViewDelegate {
func switchIsChanged(index: Int) {
list[index].isCompleted = list[index].isCompleted ? false : true
self.MyTableView.reloadData()
}
- 마지막으로 MainViewController에 Delegate위임하고, IsComplete 데이터의 따른 취소선 여부를 정하면 된다.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = MyTableView.dequeueReusableCell(withIdentifier: ToDoTableViewCell.identifier, for: indexPath) as! ToDoTableViewCell
cell.delegate = self
if target.isCompleted == true {
cell.Title.attributedText = cell.Title.text?.strikeThrough()
cell.IsCompletedSwitch.setOn(true, animated: false)
}else{
cell.IsCompletedSwitch.setOn(false, animated: false)
if let text = cell.Title.text {
let attributedString = NSMutableAttributedString(string: text)
attributedString.removeAttribute(.strikethroughStyle, range: NSMakeRange(0, attributedString.length))
cell.Title.attributedText = attributedString
}
}
}
- Delegate 개념이 쉽지 않다. 그래도 공부한대로 정리를 하자면
현재 TableViewCell 에서 직접 IBAction으로 TextLabel의 Attribute를 변경할려고 하면,
ReuseableCell이기 때문에, 정상적으로 작동을 하지 않을 가능성이 있다.
그리고 우리는 List 즉 [Todo]를 수정해야하는데, List는 TableViewCellController 에서는 접근할 방법이 없다.
왜냐하면 MainViewController에 있으니깐!
그래서 Delegate 를 사용해서 TableViewCell이 할일을 위임을 하는거다.
1. Protocol을 사용해서 어떤 일을 할건지 정의를 먼저 해야한다.
1-1. IsCompleted를 바꾸는 함수를 만들 예정
2. 해당 프로토콜 인스턴스를 Sender, 즉 위임을 할려고 하는 VC에서 생성한다.
3. 해당 프로토콜을 Receiver, 즉 위임받는 VC에서 채택한다.
4. 채택한 VC에서 Protocol을 Confirm ( 함수 구현 하기 )
5. Sender 측에서 해당 Protocol (액션)을 어떻게 트리거할지 정해서 구현
5-1 지금 케이스에서는 Switch가 눌렸을때.
- 좀 더 공부를 해봐야 하지만, 지금 시점에서는 이정도면 나름 Flow 정도는 이해를 한것 같다.
LV3
- LV3에서는 생성된 TodoList를 지워야한다.
- tableView 기본 함수중에
trailingSwipeActionsConfigurationForRowAt
함수가 있다, 물론 Leading도 있다.
- 해당 함수를 통해 cell를 Swipe했을때 동작들을 추가할 수 있다.
- 현재는 삭제 기능하면 구현하면 되지만 추가적으로 필요할 수 있다고 판단해서 Leading도 구현을 해놓았다.
func tableView(_ tableView: UITableView, leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let action = UIContextualAction(style: .normal, title: "swipe", handler: {(action, view, completionHandler) in
print("Swiped")
completionHandler(true)
})
return UISwipeActionsConfiguration(actions: [action])
}
func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let action = UIContextualAction(style: .destructive, title: "swipe", handler: {(action, view, completionHandler) in
self.list.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .fade)
completionHandler(true)
})
return UISwipeActionsConfiguration(actions: [action])
}
여기서 공부해야겠어요 알찬 정보가 빵빵하네요 :>