3월 18일 TIL (TodoList)

이승원·2024년 3월 18일
2

TIL

목록 보기
46/75
post-thumbnail

ToDoList 앱 만들기

  • 이번 주차 개인 과제는 To-Do-List를 만드는 것이다.
  • 과제를 시연영상만 봤을때는 되게 간단한 앱이지 않을까 생각했지만, TableView가 역시나 복병이었다.

LV1

  • Lv1 에서는 기본 UI를 구성해야한다.
  • TableView, 추가 버튼, 데이터 구성까지 끝내야 했다.
  • 우선 StoryBoard로 프로젝트를 생성하고, 버튼과 TableViewContraint를 설정해서 화면에 위치시켰다.
  • 여기서 첫번째 문제 Auto Layout의 문제가 발생했다.
  • 테이블뷰는 Leading, Trailing, Bottom은 전부 Safe Area에 맞춰서 하면되고, top은 버튼의 Buttom으로 하면 되서 문제가 되지 않지만, Button은 항상 오른쪽 상단에 위치해야 하는데,Leading 설정이 문제였다. Trailing , TopSafe 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 에는 UILabelUISwitch를 만들었다.
  • TableView cell 만들때 제일 오류가 나기 쉬운건 identifier이라서 따로 상수로 만들어서 , nib() 함수를 같이 만들어서 사용했다.
  • TableView delegate 및 dataSource도 ViewController에서 설정해줬다.
  • LV1 결과물은 아래와 같다.


LV2

  • LV2 에서는 Todo 추가하기 기능을 구현했다.
  • 추가하기 기능은 UIAlertController를 사용했다.
  • IBAction을 통해 버튼이 클릭시 UIAlertController를 호출하는 방식으로 구현해다.
  • UIAlertController 사용방법은 아래와 같다.

//alertController 인스턴스 생성
//title : String, message : String, preferredStyle: UIAlertController.style
let alertController = UIAlertController(title: "할 일 추가", message: "입력해주세요!", preferredStyle: .alert)

// alertController에다가 TextField 추가
alertController.addTextField()
// 버튼 (Action 추가) 
// 약간 CompletionHandler 처럼 해당 버튼이 클릭이후에 할 Action을 추가할 수 있음.
// TextField의 text를 list에 추가해준다. 
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))
        // MyTableView.reloaData()를 해줘야지만 테이블뷰가 새로고침이 되어서 
        // 추가된 새로운 Todo가 업데이트가 된다. 
		self.MyTableView.reloadData()
    }
}
//닫기 버튼 (Action) 추가
let close = UIAlertAction(title: "닫기", style: .destructive, handler: nil)
                
//UIAlertAction에는 총 3가지가 있다.
//.default -> 말그대로 디폴트 별 기능없음
//.cancel -> 현재 행동의 대해 change가 없다는 것을 보여주기 위한 스타일
//.destructive -> 현재 행동이 기존의 데이터가 삭제 혹은 취소되는 것을 보여주기 위한 스타일
//.cancel 만 두개를 사용할 수 없다 -> 이유는 모르겠음. 
// 기능이 아니라 스타일이라는걸 알아야한다. 

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을 그냥 하게 되면 제대로 작동을 하지 않을 가능성이 높다.
  • 그래서 아래와 같은 방법으로 문제를 해결했다.
  1. Protocol 생성
    • index는 indexPath 용
    • TableView에서 어떤 Cell인지 확인하기 위한 용도
protocol TableViewDelegate {
    func switchIsChanged(index: Int)
}
  1. TableViewCell에서 delegate 프로토콜 인스턴스 생성
    • delegate는 TableView에서 사용하기 위함
    • IBAction을 통해 Swift 값이 변하면 해당 프로토콜의 제약함수를 호출
var delagate : TableViewDelegate?
    
@IBAction func IsCompletedSwitchTouched(_ sender: UISwitch) {
   self.delagate?.switchIsChanged(index: index)
}
  1. 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()
    }
  1. 마지막으로 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])
    }

  • 내일은 LV4 LV5까지 끝낼야지..
profile
개발자 (진)

1개의 댓글

comment-user-thumbnail
2024년 3월 19일

여기서 공부해야겠어요 알찬 정보가 빵빵하네요 :>

답글 달기